A C++26 Compile-Time List Comprehension Framework Using Expression Templates

A C++26 Compile-Time List Comprehension Framework Using Expression Templates

Usage:

int main()
{
    using namespace ListComprehension;

    std::vector<int> nums ={1,2,3,4};
    auto squares = in(nums, each(x*x));
    Print(squares);

    auto filtered = in(nums, each(x<3,x*x));
    Print(filtered);

    std::array<int,4> array ={5,6,7,8};
    auto arraySquares = in(array, each(x*x));
    Print(arraySquares);

    std::list<int> list ={10,20,30};
    auto listSquares = in(list, each(x*x));
    Print(listSquares);

    std::set<int> set ={3,4,5};
    auto setSquares = in(set, each(x<5,x*x));
    Print(setSquares);

    std::map<int,int> map = { {1,10}, {2,20}, {3,30} };
    auto keys = in(map, each(first<std::pair<const int,int>>()));
    Print(keys);

    auto values = in(map, each(second<std::pair<const int,int>>()));
    Print(values);

    auto products = in(map,
        each(
            first<std::pair<const int,int>>() *
            second<std::pair<const int,int>>()
        )
    );
    Print(products);

    std::unordered_map<int,int> unorderedMap = { {4,40}, {5,50}, {6,60} };
    auto unorderedProducts = in(unorderedMap,
        each(
            first<std::pair<const int,int>>() *
            second<std::pair<const int,int>>()
        )
    );
    Print(unorderedProducts);

    return 0;
}
The library:

#include <iostream>
#include <vector>
#include <array>
#include <list>
#include <set>
#include <map>
#include <unordered_map>
#include <deque>
#include <string>
#include <utility>
#include <type_traits>

namespace ListComprehension
{
  // ============================================================
  // CORE LIBRARY
  // This section should eventually become ListComprehension.h
  // ============================================================

  template<typename T>
  struct ValueExpression
  {
      T value;

      template<typename Value>
      auto Evaluate(Value) const
      {
          return value;
      }
  };

  template<typename Function>
  struct FunctionExpression
  {
      Function function;
      template<typename Value>
      auto Evaluate(Value value) const
      {
          return function(value);
      }
  };

  template<typename Function>
  auto makeExpression(Function function)
  {
      return FunctionExpression<Function>{ function };
  }

  struct Placeholder
  {
      template<typename Value>
      auto Evaluate(Value value) const
      {
          return value;
      }
  };

  constexpr Placeholder x{};

  struct AlwaysTrue
  {
      template<typename Value>
      bool Evaluate(Value) const
      {
          return true;
      }
  };

  template<typename Expression, typename Condition = AlwaysTrue>
  struct EachExpression
  {
      Expression expression;
      Condition condition;
  };

  template<typename Expression>
  auto each(Expression expression)
  {
      return EachExpression<Expression>
      {
          expression,
          {}
      };
  }

  template<typename Expression, typename Condition>
  auto each(Expression expression, Condition condition)
  {
      return EachExpression<Expression, Condition>
      {
          expression,
          condition
      };
  }

  template<typename Container, typename Expression, typename Condition>
  auto in(const Container& container, EachExpression<Expression, Condition> expression)
  {
      using ResultType = decltype(expression.expression.Evaluate(*container.begin()));
      std::vector<ResultType> result;
      for (auto&& value : container)
      {
          if (expression.condition.Evaluate(value))
          {
              result.push_back(expression.expression.Evaluate(value));
          }
      }
      return result;
  }

  template<typename Container>
  void Print(const Container& container)
  {
      for (auto&& value : container)
      {
          std::cout << value << " ";
      }
      std::cout << "\n";
  }

  template<typename T>
  void Print(const std::pair<T, T>& value)
  {
      std::cout << "(" << value.first << "," << value.second << ") ";
  }

  template<typename Container>
  void PrintPairs(const Container& container)
  {
      for (auto&& value : container)
      {
          std::cout << "(" << value.first << "," << value.second << ") ";
      }
      std::cout << "\n";
  }
}
The Operators:

namespace ListComprehension
{
    template<typename Left, typename Right>
    struct MultiplyExpression
    {
        Left left;
        Right right;
        template<typename Value>
        auto Evaluate(Value value) const
        {
            return left.Evaluate(value) * right.Evaluate(value);
        }
    };

    template<typename Left, typename Right>
    auto operator*(Left left, Right right)
    {
        return MultiplyExpression<Left, Right>
        {
            left,
            right
        };
    }

    template<typename Left, typename Right>
    struct LessExpression
    {
        Left left;
        Right right;
        template<typename Value>
        auto Evaluate(Value value) const
        {
            return left.Evaluate(value) < right.Evaluate(value);
        }
    };

    template<typename Left, typename Right>
    auto operator<(Left left, Right right)
    {
        return LessExpression<Left, ValueExpression<Right>>
        {
            left,
            ValueExpression<Right>{ right }
        };
    }

    // ============================================================
    // MEMBER ACCESS
    // ============================================================
    template<typename Member>
    auto member(Member memberPointer)
    {
        return makeExpression(
            [memberPointer](auto value)
            {
                return value.*memberPointer;
            }
        );
    }

    template<typename Pair>
    auto first()
    {
        return member(&Pair::first);
    }

    template<typename Pair>
    auto second()
    {
        return member(&Pair::second);
    }
}

Tutorial

This tutorial presents a small functional programming framework implemented entirely with modern C++ templates. The goal is to create a syntax similar to list comprehensions found in languages such as Python, Haskell, and functional query languages while preserving native C++ performance and compile-time optimization.

The framework allows collection transformations to be expressed declaratively:


auto squares = in(nums, each(x*x));

auto filtered = in(nums, each(x<3,x*x));

without explicitly writing:

- loops
- temporary variables
- iterator management
- lambda functions

The library demonstrates several advanced C++ concepts:

- expression templates
- deferred evaluation
- operator overloading
- generic programming
- compile-time type deduction
- functional composition
- extensible operator systems


1. Introduction

Traditional C++ collection processing usually requires manually describing the mechanics:


std::vector<int> result;

for(auto value : nums)
{
    result.push_back(value * value);
}

The algorithm is simple, but the programmer must manually manage:

- iteration
- storage
- transformation
- filtering

Functional languages instead describe the intent:


square every element in this collection

This library attempts to bring that style to C++ while keeping the language's type system and performance model.

The target syntax is:


auto result = in(nums, each(x*x));

The programmer specifies:

- the source collection
- the transformation
- optional selection rules

The framework handles execution.


2. Library Architecture

The framework is divided into two independent sections.

2.1 Core Library

The core library contains the execution engine.

Its responsibilities are:

- storing expressions
- evaluating expressions
- iterating containers
- constructing result collections

The core library does not know about:

- multiplication
- comparisons
- string operations
- mathematical functions

It only knows one concept:

An expression is an object that can be evaluated later.


2.2 Operator Library

Operations are separated from the engine.

The operator layer provides:

- multiplication
- comparisons
- arithmetic
- string transformations
- custom user operations

The separation means new operators can be added without modifying:

- container processing
- expression evaluation
- result generation


3. Deferred Evaluation

The central idea is that expressions are not executed immediately.

Consider: (x * x)

Normally C++ expects x to contain a value.

In this framework x represents the current element that will exist later

The expression (x * x) creates an object describing:


multiply(
    placeholder,
    placeholder
)

No multiplication occurs yet.

The expression is stored and evaluated when the container is processed.


4. The Placeholder

The placeholder is the starting point of the expression system.


struct Placeholder
{
    template<typename Value>
    auto Evaluate(Value value) const
    {
        return value;
    }
};

constexpr Placeholder x{};

The placeholder represents the current item.

When evaluation happens:


x.Evaluate(10)

returns:


10

The placeholder therefore connects a future container element with the expression tree.


5. Constant Expressions

Expressions may contain both dynamic and fixed values.

Example:


x<3

contains:

- a dynamic value (x)
- a constant value (3)

Constants are represented using:


template<typename T>
struct ValueExpression
{
    T value;

    template<typename Value>
    auto Evaluate(Value) const
    {
        return value;
    }
};

The input is ignored because the value is constant.


6. Expression Trees

Every operation creates a compile-time expression tree.

For example (x * x) creates: Multiply(x, x)

A comparison x<3 creates: Less(x, 3)

The tree is only a description of the computation.

Evaluation happens later.


7. Operators

Operations are implemented as expression nodes.

Example multiplication:


template<typename Left, typename Right>
struct MultiplyExpression
{
    Left left;
    Right right;

    template<typename Value>
    auto Evaluate(Value value) const
    {
        return left.Evaluate(value) *
               right.Evaluate(value);
    }
};

The multiplication expression stores two child expressions.

When evaluated:


evaluate(left)
        *
evaluate(right)

is performed.


8. The each Operation

The each operation is the heart of the library. It represents a transformation that is applied to every element of a container and may optionally include a filtering condition.

It is represented by:


template<typename Expression, typename Condition = AlwaysTrue>
struct EachExpression
{
    Expression expression;
    Condition condition;
};

When no condition is supplied, an internal AlwaysTrue predicate is used, so every element is processed.

For example:


each(x * x)

creates a transformation that squares every element.

A filtering condition can also be supplied:


each(x * x, x < 3)

This creates a transformation that first checks whether the element satisfies the condition, and only then evaluates the expression.

Conceptually, the algorithm performed by each is:


for every element
{
    if (condition(element))
    {
        result.push_back(expression(element));
    }
}

If no condition is provided, the check always succeeds, making each(expression) equivalent to:


for every element
{
    result.push_back(expression(element));
}

For example:


auto squares = in(numbers, each(x * x));

auto smallSquares = in(numbers, each(x * x, x < 3));

The first transforms every element, while the second transforms only those elements that satisfy the condition.


10. Container Support

The framework works with any container supporting range iteration.

Examples:

- vector
- array
- list
- deque
- set
- unordered_set
- map
- unordered_map

The engine only requires:


begin()
end()

This allows the same expression system to operate over many different data structures.


11. Member Access

Associative containers introduce a special case.

A map element is:


std::pair<const Key,Value>

Therefore the user may want:


first(pair)

second(pair)

The library provides selectors:


auto keys = in(map, each(first<Pair>()));

auto values = in(map, each(second<Pair>()));

The selector system is intentionally isolated.

Future C++ reflection support can replace it with automatic member access.

The future goal is syntax such as:


each(x.first)

without changing the expression engine.


12. Function Expressions

Some operations cannot naturally be represented by operators.

The library therefore supports generic function expressions.


template<typename Function>
struct FunctionExpression
{
    Function function;

    template<typename Value>
    auto Evaluate(Value value) const
    {
        return function(value);
    }
};

This creates an extension point for:

- string operations
- mathematical functions
- user-defined transformations


13. Complete Example


std::vector<int> nums =
{
    1,2,3,4
};

auto squares = in(nums, each(x*x));

auto filtered = in(nums, each(x<3,x*x));

Execution becomes:

(container) -> (expression tree) -> (evaluate each element) -> (result vector)


14. Why This Approach Is Interesting

The framework creates a small embedded functional language inside C++.

The user writes:


each(x*x)

but the compiler sees:


MultiplyExpression
(
    Placeholder,
    Placeholder
)

This gives the library the ability to:

- inspect expressions
- optimize operations
- add new operators
- generate different execution strategies


15. Future Extensions

Possible future additions:

- C++26 reflection based member access
- lazy evaluation
- parallel execution
- SIMD optimized operators
- result container deduction
- database-style queries
- string processing pipelines
- compile-time expression optimization


16. Conclusion

This library demonstrates that C++ templates can be used to build a small functional programming language.

The main ideas are:

- expressions are data
- evaluation is delayed
- operators build trees
- containers provide input
- the engine performs execution

The result is a compact syntax:


in(nums, each(x*x));

in(nums, each(x<3,x*x));

while maintaining:

- static typing
- zero-cost abstraction
- extensibility
- native C++ performance

The architecture also leaves a clear path toward C++26 reflection, where member access can become fully automatic while preserving the same expression framework.