Function objects and std::function
A function object is a class with operator(). The compiler turns every lambda into one, so the concept is already familiar; writing the class by hand buys a name, a type that can be overloaded, and state that is visible in the class definition.
std::function is the other half of the lesson: a type that can hold any callable with a given signature, at the cost of one indirection and a possible allocation. It is how a class stores a callback whose real type is decided by the caller.
A class with operator()
The adder below stores a value and adds it in operator(). An object of the class can be called like a function, which is what the algorithm in main expects.
The program creates two adders with different values and passes one to std::transform. The state is part of the object, so the same algorithm runs with different behaviour from one type.
A function object can be overloaded on its parameters, which a lambda cannot, and it can have named constructors and members. The two forms are interchangeable wherever a callable is needed.
#include <algorithm>
#include <iostream>
#include <vector>
class adder
{
public:
explicit adder(int amount) : amount_(amount) {}
int operator()(int value) const
{
return value + amount_;
}
private:
int amount_;
};
int main()
{
std::vector<int> values{1, 2, 3};
adder add_five{5};
std::transform(values.begin(), values.end(), values.begin(), add_five);
for (int value : values) {
std::cout << value << ' ';
}
std::cout << '\n';
}
g++ -std=c++23 -Wall -Wextra functor.cpp -o functor
./functor
6 7 8
State between calls
An object passed to a standard algorithm by value is copied, and the algorithm works on its own copy. A functor that counts its calls therefore counts inside the algorithm, and the original in main is unchanged unless the algorithm takes a reference.
The program counts how many values are even with a functor that keeps a counter, and passes it with std::for_each. The count printed afterwards is the one from the object main kept, because for_each returned the functor it used.
Capturing the return value is the pattern: auto result = std::for_each(...) gives back the algorithm's copy with its state. A lambda with a mutable capture works the same way.
#include <algorithm>
#include <iostream>
#include <vector>
class even_counter
{
public:
void operator()(int value)
{
if (value % 2 == 0) {
++count_;
}
}
int count() const
{
return count_;
}
private:
int count_ = 0;
};
int main()
{
std::vector<int> values{1, 2, 4, 7, 8};
even_counter counter = std::for_each(values.begin(), values.end(), even_counter{});
std::cout << counter.count() << '\n';
}
g++ -std=c++23 -Wall -Wextra state.cpp -o state
./state
3
std::function holds any callable
std::function<int(int)> is a type that can store a function pointer, a lambda or a functor with that signature. Assigning replaces the stored object, and calling goes through one indirection.
The program stores a lambda, then a function pointer, then a functor in the same variable. Each call prints the result of the current target, and the captured state of the lambda travels with it.
A std::function that holds a capturing lambda may allocate to store the capture. The next section measures the size; the rule is to use std::function where the type has to be erased and a template parameter where it does not.
#include <functional>
#include <iostream>
int triple(int value)
{
return value * 3;
}
int main()
{
std::function<int(int)> operation;
int bonus = 100;
operation = [bonus](int value) { return value + bonus; };
std::cout << operation(1) << ' ';
operation = triple;
std::cout << operation(1) << ' ';
operation = [](int value) { return value * value; };
std::cout << operation(4) << '\n';
}
g++ -std=c++23 -Wall -Wextra std_function.cpp -o std_function
./std_function
101 3 16
A comparator object
A comparator is a function object with a two-argument operator() that answers whether the first argument comes before the second. The standard library ships std::less and std::greater, and a project writes its own when the order is domain-specific.
The program sorts records by score, highest first, with a named comparator. The object has no state, so it could be a lambda; the class is worth it when the comparator has a name the error messages and the reader can use.
The comparator must be a strict weak ordering, as lesson 051 explained. A comparator that compares the wrong field produces a sorted range that looks plausible and is wrong.
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
struct record
{
std::string name;
int score;
};
struct by_score
{
bool operator()(const record& a, const record& b) const
{
return a.score > b.score;
}
};
int main()
{
std::vector<record> records{{"ada", 10}, {"bob", 30}, {"cyd", 20}};
std::sort(records.begin(), records.end(), by_score{});
for (const auto& r : records) {
std::cout << r.name << ' ';
}
std::cout << '\n';
}
g++ -std=c++23 -Wall -Wextra compare.cpp -o compare_functor
./compare_functor
bob cyd ada
What the wrappers cost
A lambda and a functor are ordinary objects: their size is the size of their captures, and a call to them can be inlined because the type is known. A std::function is a fixed-size wrapper, usually four pointers, that stores small callables inside itself and allocates for larger ones.
The program prints the three sizes on this build: an empty lambda and an empty functor are one byte, and the std::function is thirty-two. A capturing lambda grows by its captures, and the std::function does not.
The indirect call inside std::function cannot be inlined, so a hot loop that calls millions of times pays for it. Use std::function at an API boundary and a template parameter inside the implementation.
#include <functional>
#include <iostream>
struct functor
{
int operator()(int value) const
{
return value;
}
};
int main()
{
auto lambda = [](int value) { return value; };
std::function<int(int)> wrapped = lambda;
std::cout << sizeof(lambda) << ' ' << sizeof(functor{}) << ' '
<< sizeof(wrapped) << '\n';
}
g++ -std=c++23 -Wall -Wextra cost.cpp -o cost
./cost
1 1 32
Exercises
- Write a functor that multiplies by a factor given at construction and use it with std::transform.
- Store a lambda, a function pointer and a functor in one std::function variable and print the result of each call.
- Write a comparator object that orders records by name and use it to sort the vector in compare.cpp.
Questions
A new set of questions in a new order every time. Choose one answer for each, then check.
-
What is a function object?
Answer
An object with an operator()
The class defines the call operator, so an instance can be called like a function.
-
What does a function object store that a function pointer cannot?
Answer
State in its members
The object has members, which the call operator can read and change.
-
What does
std::functionrequire of its target?Answer
That it is copyable and callable with the signature
The wrapper copies the callable into its storage.
-
What does this program print?
#include <iostream> struct add { int operator()(int a, int b) const { return a + b; } }; int main() { add plus; std::cout << plus(1, 5) << ' ' << plus(2, 5) << ' ' << plus(3, 5); }Answer
6 7 8
The call operator adds its arguments, and the three calls print 6, 7 and 8.
-
What does this program print?
#include <iostream> struct counter { int value = 0; int operator()() { return ++value; } }; int main() { counter next; std::cout << next() << ' ' << next() << ' ' << next(); }Answer
1 2 3
The state lives in the object, so every call increments the same member.
-
What does this program print?
#include <functional> #include <iostream> int main() { std::function<int(int)> twice = [](int v) { return v * 2; }; std::function<int(int)> plusOne = [](int v) { return v + 1; }; std::cout << twice(50) + 1 << ' ' << plusOne(2) << ' ' << twice(8); }Answer
101 3 16
The two std::function objects hold different lambdas, and each call reaches its own target.
-
What does this program print?
#include <algorithm> #include <iostream> #include <string> #include <vector> struct by_length { bool operator()(const std::string& a, const std::string& b) const { return a.size() < b.size(); } }; int main() { std::vector<std::string> words{"ada", "cyd", "bob"}; std::stable_sort(words.begin(), words.end(), by_length{}); for (const auto& w : words) std::cout << w << ' '; }Answer
ada cyd bob
All three names have the same length, so the stable sort keeps the original order.
-
What does this program print?
#include <functional> #include <iostream> #include <string> int main() { std::function<int(int)> call = [](int v) { return v; }; std::cout << sizeof(call) << ' ' << (call.target<int(*)(int)>() == nullptr) << ' ' << sizeof(std::string); }Answer
32 1 32
The wrapper object is larger than a pointer, holds a lambda rather than a function pointer, and the string is thirty-two bytes.
-
What does the compiler say about this line?
#include <functional> int main() { std::function<int(int)> f = [](int v) { return v; }; return f("text"); }Answer
no match for call to '(std::function<int(int)>) (const char [5])'
The wrapper's signature takes an int, and a string literal does not convert to it.
-
When is a plain function pointer better than std::function?
Answer
When the callable is a free function and the call is hot
The pointer is one word and the call is direct, with no type erasure.
The code on this page was built with g++ 15.2.0 and run on Alpine Linux 3.24.3 (x86_64) in a Docker container.
Article author: Arthur Isaev