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

  1. Write a functor that multiplies by a factor given at construction and use it with std::transform.
  2. Store a lambda, a function pointer and a functor in one std::function variable and print the result of each call.
  3. 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.

  1. 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.

  2. 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.

  3. What does std::function require of its target?

    Answer

    That it is copyable and callable with the signature

    The wrapper copies the callable into its storage.

  4. 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.

  5. 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.

  6. 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.

  7. 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.

  8. 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.

  9. 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.

  10. 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

Banner image