Consider the following program 14/functor that copies a vector v1 into v2
using a back_insert_iterator and prints out v2 twice using For_Each():
template <typename I, typename F>
void For_Each(I b, I e, F f) {
while (b != e) {
f(*b);
++b;
}
}
void print_int(int x) { cout << x << ' '; }
struct IntPrinter {
void operator()(int x) const { cout << x << ' '; }
};
int main() {
vector<int> v1 { 100, 101, 102, 103, 104 };
vector<int> v2;
// Function object (aka functor)
copy(v1.begin(), v1.end(), back_inserter(v2));
For_Each(v2.begin(), v2.end(), &print_int); cout << '\n';
For_Each(v2.begin(), v2.end(), IntPrinter{}); cout << '\n';
}
We’ve seen the first invocation of For_Each() using a function pointer before.
The second invocation of For_Each() is new; we construct an IntPrinter
object and pass it to the For_Each() function template as f. The call to
f(*b) in For_Each() invokes the operator() member function of the
IntPrinter object. A class that implements operator() and can therefore be
invoked is called a function object or functor. Unlike a function
pointer, a functor can store state information, as we’ll see later.
Shown below is an incorrect attempt to negate the integers in v2 before
printing them out:
int main() {
vector<int> v1 { 100, 101, 102, 103, 104 };
vector<int> v2;
// Stateless functors
copy(v1.begin(), v1.end(), back_inserter(v2));
For_Each(v2.begin(), v2.end(), negate<int>{});
For_Each(v2.begin(), v2.end(), IntPrinter{}); cout << '\n';
}
std::negate is an STL functor that defines operator() as follows:
constexpr T operator()(const T& arg) const {
return -arg;
}
Since For_Each() simply invokes f(*b) without assigning the result back to
the element, the For_Each() call has no effect.
Instead, we can use the transform() algorithm to modify each element as we
copy v1 to v2:
int main() {
vector<int> v1 { 100, 101, 102, 103, 104 };
vector<int> v2;
// Stateless functors
transform(v1.begin(), v1.end(), back_inserter(v2),
negate<int>{});
For_Each(v2.begin(), v2.end(), IntPrinter{}); cout << '\n';
}
Here’s a possible implementation for transform():
template <typename SrcIter, typename DestIter, typename F>
DestIter Transform(SrcIter b_src, SrcIter e_src, DestIter b_dest, F f) {
while (b_src != e_src) {
*b_dest = f(*b_src);
++b_dest;
++b_src;
}
return b_dest;
}
Let’s say we wanted to apply the function f(y) = 300 + y to each element of
v1 as we copy it over to v2. Below, we define a functor AddX that stores a
member variable x and has operator() defined to return the sum of x and a
given integer y:
struct AddX {
int x;
AddX(int x) : x(x) {}
int operator()(int y) const { return x + y; }
};
int main() {
vector<int> v1 { 100, 101, 102, 103, 104 };
vector<int> v2;
// Stateful functors
transform(v1.begin(), v1.end(), back_inserter(v2),
AddX{300});
For_Each(v2.begin(), v2.end(), IntPrinter{}); cout << '\n';
}
The instance AddX{300} is an example of a stateful functor that carries around
the number 300 to use every time operator() is invoked.
In fact, we can further generalize our functor to carry around the function to call:
template <typename F>
struct OpX {
F op;
int x;
OpX(F op, int x) : op(op), x(x) {}
int operator()(int y) const { return op(x, y); }
};
int main() {
vector<int> v1 { 100, 101, 102, 103, 104 };
vector<int> v2;
// Stateful functors
transform(v1.begin(), v1.end(), back_inserter(v2),
OpX{plus<int>{}, 300});
For_Each(v2.begin(), v2.end(), IntPrinter{}); cout << '\n';
}
The OpX functor is a class template parameterized by any invocable type F.
An instance of OpX holds a function object op of type F in addition to one
number x to call the function object with. Its operator() will call op
with x and a given integer y.
Here, we create an instance of OpX that holds the std::plus functor and the
integer 300. std::plus is a binary functor, i.e., it takes two arguments.
OpX binds the first argument of std::plus to 300, effectively yielding a
unary functor that can be called by transform() with a single argument.
STL provides a function template, std::bind_front(), which generalizes this
pattern of holding a functor and a partial list of its arguments. For example,
assuming f is a functor that takes five arguments,
auto g = bind_front(f, arg1, arg2, arg3) would return a functor g that takes
two arguments. In other words, f(arg1, arg2, arg3, arg4, arg5) is equivalent
to g(arg4, arg5).
We can rewrite our program to use bind_front() as follows:
int main() {
vector<int> v1 { 100, 101, 102, 103, 104 };
vector<int> v2;
// Stateful functors
transform(v1.begin(), v1.end(), back_inserter(v2),
bind_front(plus<int>{}, 300));
For_Each(v2.begin(), v2.end(), IntPrinter{}); cout << '\n';
}
Understanding how bind_front() works requires additional template machinery
that we have yet to study. We’ll revisit this machinery in later chapters.
std::bind_front() was introduced in C++20 as a streamlined replacement for
std::bind(), which has been around since C++11. std::bind() was designed to
support complex bind operations – it can bind any of the functor’s arguments in
any order and even compose multiple functors. std::bind_front() doesn’t
support these features, but it is more efficient and easier to use than
std::bind() for the common case of binding a partial list of arguments to a
functor.
std::bind() uses what’s known as “placeholders” to specify unbound arguments
that will later be passed to the functor it returns. Consider the following
example:
struct SubSubSub {
int operator()(int a, int b, int c, int d) const {
return a - b - c - d;
}
};
auto f = bind(SubSubSub{}, 1000,
placeholders::_2, placeholders::_1, placeholders::_1);
cout << f(100, 500) << '\n';
This snippet will compute 1000 - 500 - 100 - 100 and print 300. The
SubSubSub functor takes four arguments: a, b, c, and d. We bind the
first argument a to 1000 but leave the other three arguments unbounded by
passing placeholder objects. The std::placeholders namespace defines global
variabes _1, _2, etc., that we can use to specify how the arguments to the
functor f will be passed to the SubSubSub functor. In our example, the first
argument to f will be routed to c and d, and the second argument will be
routed to b.
We can rewrite the bind_front() call we used in the last section using
bind() as follows:
int main() {
vector<int> v1 { 100, 101, 102, 103, 104 };
vector<int> v2;
// Bind expressions
transform(v1.begin(), v1.end(), back_inserter(v2),
bind(plus<int>{}, 300, placeholders::_1));
For_Each(v2.begin(), v2.end(), IntPrinter{}); cout << '\n';
}
std::bind() also supports function composition. Suppose we want to implement a
functor for f(x) = 300 + g(x), where g(x) = -x. std::bind() lets us
compose f(x) using std::plus and std::negate as follows:
int main() {
vector<int> v1 { 100, 101, 102, 103, 104 };
vector<int> v2;
// Bind expressions
transform(v1.begin(), v1.end(), back_inserter(v2),
bind(plus<int>{}, 300,
bind(negate<int>{}, placeholders::_1)));
For_Each(v2.begin(), v2.end(), IntPrinter{}); cout << '\n';
}
Here, we are passing the result of a bind() call, known as a
bind expression, as an argument to another bind() call, which results in
function composition. Placeholders used in a nested bind expression refer to the
arguments that will later be passed to the overall bind expression.
C++11 introduced lambda expressions, which return unnamed functors. Let us replace the messy nested bind expression from the previous section with a lambda expression:
int main() {
vector<int> v1 { 100, 101, 102, 103, 104 };
vector<int> v2;
// Lambda expressions
transform(v1.begin(), v1.end(), back_inserter(v2),
[] (int x) {
return 300 + -x;
}
);
For_Each(v2.begin(), v2.end(), IntPrinter{}); cout << '\n';
}
A lambda expression starts with [], which is known as the lambda introducer,
and is followed by what looks like a function definition.
The lambda introducer can optionally contain captures, which specify local variables to be captured by the lambda. Here’s an example:
int main() {
vector<int> v1 { 100, 101, 102, 103, 104 };
vector<int> v2;
// Lambda expressions
int a = 2, b = 100;
auto lambda = [&a, b] (int x) {
return a * x + b;
};
a = 4, b = 1'000'000;
transform(v1.begin(), v1.end(), back_inserter(v2), lambda);
For_Each(v2.begin(), v2.end(), IntPrinter{}); cout << '\n';
}
The syntax [&a, b] means that the lambda captures the local variable a by
reference and b by value. In the example above, the values of the local
variables a and b are 2 and 100, respectively, at the time the lambda is
constructed. This effectively creates a closure, a generic term in
programming languages referring to a function object that captures some
variables in scope.
After the lambda construction, we change a to 4 and b to 1'000'000,
respectively. (Note that C++ lets you place single quotes in between digits for
the sake of readability.) The change to a is reflected in the lambda because
the lambda object holds a reference to a, but the change to b is not
reflected because the lambda object holds a copy of b. The program produces
the following output:
500 504 508 512 516
Be careful when capturing local variables by reference. If the lambda outlives the captured variable, the lambda will hold a dangling reference, resulting in undefined behavior.
Note that we used auto for the type of the variable lambda. That is because
we do not know that type of the functor that the lambda expression returns. The
compiler will generate a unique type for each lambda expression on the fly.
For the lambda defined above, for example, we can imagine that the compiler will
generate the following class L1 on the fly:
int main() {
vector<int> v1 { 100, 101, 102, 103, 104 };
vector<int> v2;
// Translating lambda to a class
struct L1 {
int& a;
int b;
L1(int& a, int b) : a(a), b(b) {}
int operator()(int x) const { return a * x + b; }
};
int a = 2, b = 100;
auto lambda = L1{a, b};
a = 4, b = 1'000'000;
transform(v1.begin(), v1.end(), back_inserter(v2), lambda);
For_Each(v2.begin(), v2.end(), IntPrinter{}); cout << '\n';
}
Reframing lambda and closure as a concrete functor class that we’re familiar with unveils the mystery around them!
C++20 introduces the ranges libary. A range is a generalization of a
sequence of elements in STL denoted by a half-open interval, [begin, end).
std::ranges::range is a concept defined as follows:
template <typename T>
concept range = requires(T& t) {
ranges::begin(t);
ranges::end(t);
};
T is a range if it has a begin iterator and an end sentinel, which is a
generalization of the end iterator. This means that ranges::end() does not
have to return an iterator. It just has to return an object that implements
operator==() to test if a given iterator has reached the end of the range. All
STL containers, because they have begin and end iterators, are naturally ranges.
The ranges library contains range adapters, also known as views. A view is a lightweight, read-only range that doesn’t own its elements, but wraps another range instead. A view contains a rule to apply to each element as it gets read from the underlying range, like applying a transformation.
We can rewrite our f(x) = 300 + -x transformation using the ranges library as
follows:
int main() {
vector<int> v1 { 100, 101, 102, 103, 104 };
vector<int> v2;
// Ranges
auto v1_view = ranges::transform_view{
ranges::transform_view{v1, negate<int>{}},
bind_front(plus<int>{}, 300)
};
ranges::copy(v1_view, back_inserter(v2));
ranges::for_each(v2, IntPrinter{}); cout << '\n';
}
We first construct a transform_view object with a range, our vector v1, and
a rule, the functor negate. We then create another transform_view with the
previous view as the range and the functor returned by bind_front() as the
rule.
The ranges namespace provides range versions of the STL algorithms we’ve been
working with. The new versions operate on ranges instead of begin & end iterator
pairs. We use ranges::copy() to copy the elements of v1_view into v2. As
v1_view is read by copy(), the transformations held in the view are applied
to each element. Finally, we use ranges::for_each() to print out each element
of v2.
STL overloads operator|(), which enables us to rewrite the nested view
construction as a pipeline of views, as shown below:
int main() {
vector<int> v1 { 100, 101, 102, 103, 104 };
vector<int> v2;
// Ranges
auto v1_view = v1 | views::transform(negate<int>{})
| views::transform(bind_front(plus<int>{}, 300));
ranges::copy(v1_view, back_inserter(v2));
ranges::for_each(v2, IntPrinter{}); cout << '\n';
}
Here, views::transform() is a function template that takes a functor and
returns what’s called a range adapter closure. The pipeline operator is
overloaded to take a range R and a range adapter closure C such that R | C
is equivalent to C(R). R | C returns a new range which can then be fed into
the next pipeline operator. The upshot is that the expression
v1 | views::transform(negate<int>{}) is equivalent to
ranges::transform_view{v1, negate<int>{}}.
Instead of using ranges::copy() to back-insert each element of the view from
the pipeline into our vector v2, we can append ranges::to<vector<int>>(),
introduced in C++23, to the pipeline to create a new vector:
int main() {
vector<int> v1 { 100, 101, 102, 103, 104 };
vector<int> v2;
// Ranges
auto v3 = v1 | views::transform(negate<int>{})
| views::transform(bind_front(plus<int>{}, 300))
| ranges::to<vector<int>>();
ranges::for_each(v3, IntPrinter{}); cout << '\n';
}
Here, ranges::to<vector<int>>() returns a range adapter closure, which will
back-insert each element of the view from the pipeline into a newly constructed
vector and return it by value. v3 gets move-constructed from the returned
vector.
Finally, the pipeline below demonstrates a few more features of the ranges library:
int main() {
auto v = views::iota(100)
| views::take(5)
| views::transform(negate<int>{})
| views::transform(bind_front(plus<int>{}, 300))
| views::filter([](int x) { return x % 2 == 0; });
ranges::for_each(v, IntPrinter{}); cout << '\n';
}
The program’s output is as follows:
200 198 196
We start our view pipeline with views::iota(100), which represents the
infinite range of integers starting from 100. We can imagine that it is
implemented using a begin iterator that starts at 100 and a sentinel object
whose operator==() always evaluates to false, effectively creating an infinite
range. Such ranges that generate their elements on the fly are known as
range factories or generators.
We then chain views::take(5), which will take up to 5 elements from the
beginning of the source range. After our two previously discussed
transformations of negating and adding 300, we apply a filter view that keeps
only those elements that satisfy the given predicate. We pass a lambda that
checks if a given element is an even number.
The resulting view pipeline v is evaluated lazily. That is, the series of
transformations only happen as ranges::for_each() attempts to read each
element from the view.
Last updated: 2025-11-11