Lambdas and Callable Objects

std::function

Sometimes a program needs to store in a single variable different kinds of callable values at different points in time. At one time it may need to hold a plain old function, the next moment a lambda, then perhaps later a function object. The type of that variable will have to be able to hold them all so long as they have the same set of parameters and the same return type. The C++ standard library does offer such a facility with std::function.

std::function is a general purpose wrapper for any callable that has a specified signature. Once a callable is wrapped inside a std::function object, that wrapper can be invoked using normal call syntax, which in turn invokes the enclosed callable.

Here’s the simplest case, wrapping a free function.

#include <functional>
#include <iostream>

void sayHello()
{
    std::cout << "hello\n";
}

int main()
{
    std::function<void()> f{ sayHello };
    f();
    // Return the computed value to the caller
    return 0;
}

The template argument void() indicates a signature of a function taking no argument and returning nothing. The variable f is then initialized to hold the free function sayHello. When we call f(), this executes whatever callable is stored inside f, which is sayHello in this case. f is of type std::function<void()>, and it’s able to hold a free function of type void() because they have matching signatures.

We can change what callable object f stores. This same object can hold a lambda function with the same signature.

#include <functional>
#include <iostream>

void sayHello()
{
    std::cout << "hello\n";
}

int main()
{
    std::function<void()> f{ sayHello };
    f();

    f = []() { std::cout << "from a lambda\n"; };
    f();

    return 0;
}

After printing hello, we overwrite the contents of f with a lambda function. This lambda also takes no arguments and returns nothing. When we subsequently call f(), the lambda is executed instead. The type of f itself, however, has not changed.

A std::function object can also hold no callable at all. An empty wrapper object throws an exception if we try to call it. We can check whether an object currently holds a callable object by evaluating the object in a boolean context using operator bool.

#include <functional>
#include <iostream>

int main()
{
    std::function<void()> f{};

    if (f)
        std::cout << "has a target\n";
    else
        std::cout << "empty\n";

    return 0;
}

We default construct a std::function. In C++, this creates an empty wrapper. When evaluated in the condition, operator bool returns false, so the program executes the code in the else block. Calling an empty std::function wrapper, on the other hand, would throw an exception of type std::bad_function_call.

Since std::function is a standard type, it can be used as the type of a function parameter. A function that is meant to be callable via a variety of callable objects having a specific signature may take a std::function as a parameter.

#include <functional>
#include <iostream>

// Define or declare this function with the shown parameters
void runTwice(std::function<void()> fn)
{
    fn();
    fn();
}

int main()
{
    runTwice([]() { std::cout << "tick\n"; });
    return 0;
}

The parameter fn has the signature void(). The call to runTwice passes a lambda which matches that signature. Inside the function, the parameter fn is called twice, causing the program to print tick twice. Other callable types, including free functions and function objects, could be passed to runTwice without changing its definition so long as their signatures match the signature specified in the std::function object.

It is important to follow the signature within the angle brackets exactly. std::function<int(int)> means you will store a callable that accepts one integer parameter and returns an integer. If you try to store a callable with a different signature, you will receive a compile time error. The wrapper handles the concrete type erasure itself by creating a uniform interface through which any compliant callable can be invoked. This uniformity comes at a slight expense in size and execution speed over a direct call, but is often worthwhile for managing multiple callable types.

In brief, std::function provides a uniform type to store any callable that meets a specific signature, allowing us to pass and call different types of callables as though they were all the same.

Exercise: std::functionStarter code, hints and solutionOpenClose
Guided practiceBeginner · 10–15 min

Practice: std::function

Work through the mission, reveal hints only when needed, and compare your result with the model solution.

Your challenge

Add a separate std::function<void(const std::string&)> holding a greeting function and call it with Ada. Keep the existing void() wrapper and distinguish their signatures.

Mission checklist

  1. Run the starter as a baseline, then predict the change requested in Your challenge.
  2. Make that change in your editor; use the lesson-specific hints if you need help.
  3. Compile and run your answer, then compare with the worked solution and explain the result.

Starter program

This is the working baseline, not the completed answer. Compile as C++20, then make the change described above.

#include <functional>
#include <iostream>

void sayHello()
{
    std::cout << "hello\n";
}

int main()
{
    std::function<void()> f{ sayHello };
    f();

    f = []() { std::cout << "from a lambda\n"; };
    f();

    return 0;
}

Hints

Hint 1

std::function<void()> holds a callable taking no arguments and returning no value. Both sayHello and the captureless lambda match that signature.

Hint 2

The new wrapper's signature must include its string argument. Include <string> and give the wrapped function the same parameter and return types.

Worked solution

The existing void() wrapper remains unchanged. A separate std::function with a string parameter wraps greet and is called with Ada. Its signature explicitly permits the new argument; passing that argument to the original f would be invalid.

#include <string>
#include <functional>
#include <iostream>

void sayHello()
{
    std::cout << "hello\n";
}

void greet(const std::string& name)
{
    std::cout << "Hello, " << name << '\n';
}

int main()
{
    std::function<void()> f{ sayHello };
    f();

    f = []() { std::cout << "from a lambda\n"; };
    f();

    std::function<void(const std::string&)> greeting{greet};
    greeting("Ada");
    return 0;
}

Example output

hello
from a lambda
Hello, Ada

0 comments

No comments yet. Be the first.

Add a comment

About CppLearn

CppLearn is an interactive, free reference for modern C++. In each lesson, you'll find out how the language works, rather than just lines of code that you can copy and paste. We cover concepts starting from beginner lessons on fundamentals and proceed to templates, move semantics, smart pointers, and areas that usually trap or scare C++ learners.