Functional Programming in C++

C++ is not a functional language, but modern C++ gives you lambdas, std::function, and a full algorithms library that let you write in a functional style whenever it makes code clearer than a hand-rolled loop.

Before C++11, 'passing a behavior' to another piece of code meant either a raw function pointer or a hand-written functor class with an overloaded operator(), and both were clunky enough that most C++ code just wrote loops instead of calling std::sort with a custom comparator. Lambdas changed that overnight: a function can now be defined inline, exactly where it's used, capturing the surrounding variables it needs. Combined with std::function for storing any callable uniformly, std::bind and std::invoke for adapting and calling callables generically, and an algorithms library (<algorithm>, <numeric>) built entirely around accepting callables as parameters, C++ supports a genuinely functional style alongside its object-oriented and procedural ones. This page covers what a callable actually is in C++, how lambdas compile down to ordinary classes, when to reach for std::function versus a template parameter, and the higher-order algorithms that replace raw loops with intent-revealing one-liners.

What Counts as a Callable

In C++, a 'callable' is anything that can appear on the left of a function-call syntax f(args). That includes ordinary functions, function pointers, objects of a class that overloads operator() (called function objects or functors), lambda expressions, and pointers to member functions. The algorithms library and std::function are written generically enough to accept any of these interchangeably.

is_even and pred(4) are both callables even though one is a free function and the other is an object — the calling syntax f(args) is what unifies them, not their underlying type.

#include <iostream>

// 1. An ordinary function
bool is_even(int n) { return n % 2 == 0; }

// 2. A function object (functor): a class with operator()
struct IsEven
{
    bool operator()(int n) const { return n % 2 == 0; }
};

int main()
{
    bool a = is_even(4);        // ordinary function call
    IsEven pred;
    bool b = pred(4);           // pred(4) invokes operator()
    std::cout << a << " " << b << "\n";
}

Function Objects: Stateless vs Stateful Functors

A functor becomes genuinely useful once it carries state between calls, something a plain function pointer can't do. IsEven above is a stateless functor — every instance behaves identically and it could just as easily have been a free function. A stateful functor stores data in member variables that its operator() reads or updates, giving it memory across invocations.

Each call to acc(n) mutates acc.total, so the object accumulates a running sum across calls — behavior a stateless function could never provide because a plain function has no private storage of its own between invocations.

This is exactly the shape a lambda with a capture compiles down to: the compiler generates a small class with member variables for the captured data and an operator() for the body. Understanding functors first makes lambdas far less mysterious.

#include <iostream>

class Accumulator
{
public:
    void operator()(int n) { total += n; }
    int total = 0;
};

int main()
{
    Accumulator acc;
    acc(10);
    acc(20);
    acc(30);
    std::cout << acc.total << "\n";   // 60
}

Lambdas: Anonymous Function Objects

A lambda expression [capture](params) -> return_type { body } is syntactic sugar that the compiler expands into an anonymous class with an operator() — a closure type. The capture clause controls which surrounding variables the lambda's generated class stores as members, and how.

[threshold] captures threshold by value into the closure, so the generated class effectively looks like struct { int threshold; bool operator()(int n) const { return n > threshold; } };. The lambda works exactly like the Accumulator functor above, just written inline.

threshold = 10 is an ordinary local variable.

[threshold] copies its current value (10) into the lambda's hidden class as a member.

above_threshold(15) evaluates 15 > 10, which is true.

above_threshold(5) evaluates 5 > 10, which is false.

Changing threshold after the lambda is created would NOT affect above_threshold, since the capture is by value.

Capture by reference ([&threshold] or [&] for everything used) stores a reference instead of a copy, so the lambda sees later changes to the original variable — but that also means the lambda must not outlive the variable it references, or it becomes dangling.

Because multiplier is captured by reference, the second call to scaled_sum picks up the new value 10 instead of the value that existed when the lambda was created. A by-value capture ([multiplier]) would have printed 30 both times.

#include <iostream>

int main()
{
    int threshold = 10;

    auto above_threshold = [threshold](int n) { return n > threshold; };

    std::cout << above_threshold(15) << "\n";   // 1 (true)
    std::cout << above_threshold(5)  << "\n";   // 0 (false)
}
#include <iostream>
#include <vector>

int main()
{
    std::vector<int> data{1, 2, 3, 4, 5};
    int multiplier = 2;

    auto scaled_sum = [&multiplier](const std::vector<int> &v)
    {
        int total = 0;
        for (int x : v)
            total += x * multiplier;
        return total;
    };

    std::cout << scaled_sum(data) << "\n";   // 30
    multiplier = 10;
    std::cout << scaled_sum(data) << "\n";   // 150 -- sees the updated value
}

mutable Lambdas and Generic Lambdas

operator() on a lambda's closure type is const by default, so a by-value capture can be read but not modified inside the body. Adding the mutable keyword removes that const, letting the lambda modify its own captured copies (not the originals) across calls -- useful for a lambda that needs to remember state between invocations, like a counter.

[count = 0] is an init-capture (C++14) that creates a new member named count inside the closure, initialized to 0 -- it doesn't need to correspond to any existing outer variable. mutable lets ++count modify that member, and because counter is a single object, the modification persists across calls.

Since C++14, a lambda parameter can also be declared auto, making the lambda generic -- the compiler generates a distinct operator() template instantiation for each different argument type it's actually called with, exactly like a function template.

add's operator() is effectively template<class T, class U> auto operator()(T a, U b) const { return a + b; } -- one lambda, multiple instantiations, resolved at compile time based on the argument types actually used.

#include <iostream>

int main()
{
    auto counter = [count = 0]() mutable
    {
        return ++count;
    };

    std::cout << counter() << "\n";   // 1
    std::cout << counter() << "\n";   // 2
    std::cout << counter() << "\n";   // 3
}
auto add = [](auto a, auto b) { return a + b; };

int i = add(2, 3);          // int + int
double d = add(2.5, 1.5);   // double + double

std::function: Type-Erased Storage for Any Callable

A lambda's closure type is unique and unnamed -- two lambdas with identical bodies still have different types. That makes it impossible to declare a variable, class member, or function parameter of 'the lambda's type' directly whenever the callable needs to be stored, reassigned, or passed across translation units. std::function<R(Args...)> solves this by type-erasing any callable with a matching signature into one concrete type.

The same std::function<int(int)> variable holds a free function, then a lambda, then a functor, one after another -- std::function erases the concrete type of whatever it's holding and exposes only the call signature int(int).

#include <functional>
#include <iostream>

int square(int n) { return n * n; }

int main()
{
    std::function<int(int)> op;

    op = square;                              // plain function
    std::cout << op(4) << "\n";               // 16

    op = [](int n) { return n * n * n; };     // lambda
    std::cout << op(4) << "\n";               // 64

    struct Doubler { int operator()(int n) const { return n * 2; } };
    op = Doubler{};                           // functor
    std::cout << op(4) << "\n";               // 8
}

std::bind, std::invoke, and std::ref

std::bind (from <functional>) creates a new callable by fixing some arguments of an existing one, using placeholders like std::placeholders::_1 for the arguments left open. It predates lambdas and is largely superseded by them in modern code -- a lambda is almost always clearer -- but it still appears in older codebases and interview questions.

std::bind(add, 5, _1) produces a callable that always passes 5 as add's first argument and forwards whatever it's called with as the second -- _1 marks where that forwarded argument slots in. The lambda version is shorter, easier to read, and avoids std::bind's often confusing error messages, which is why most style guides now prefer lambdas.

std::invoke (C++17, <functional>) is a uniform way to call any callable -- free function, lambda, functor, or pointer-to-member-function -- through the exact same syntax, which matters most inside generic templates that don't know in advance what kind of callable they were handed.

std::invoke(mfp, w) hides the awkward (w.*mfp)() syntax pointer-to-member calls otherwise require, and the same std::invoke(f, args...) call form also works for ordinary functions, lambdas, and functors -- exactly the uniformity generic algorithm code needs.

std::ref and std::cref (<functional>) wrap a value so it's passed by reference where a function template would otherwise copy it -- most commonly with std::bind or std::thread, both of which copy their arguments by default.

Without std::ref, std::bind would copy x by value into its internal storage, and increment would modify that internal copy instead of the caller's x. std::ref(x) wraps x in a std::reference_wrapper<int>, which std::bind stores and then implicitly converts back to int& when calling increment, so the real x is the one that changes.

#include <functional>
#include <iostream>

int add(int a, int b) { return a + b; }

int main()
{
    using namespace std::placeholders;

    auto add_five = std::bind(add, 5, _1);
    std::cout << add_five(10) << "\n";   // 15, same as add(5, 10)

    // The equivalent, and generally preferred, lambda:
    auto add_five_lambda = [](int b) { return add(5, b); };
    std::cout << add_five_lambda(10) << "\n";   // 15
}
#include <functional>
#include <iostream>

struct Widget
{
    int value = 42;
    int get_value() const { return value; }
};

int main()
{
    Widget w;

    // Calling a pointer-to-member-function normally requires (w.*ptr)() syntax:
    int (Widget::*mfp)() const = &Widget::get_value;
    std::cout << (w.*mfp)() << "\n";                 // 42

    // std::invoke handles it uniformly, same syntax as any other callable:
    std::cout << std::invoke(mfp, w) << "\n";         // 42
    std::cout << std::invoke(&Widget::value, w) << "\n"; // 42, works for data members too
}
#include <functional>
#include <iostream>

void increment(int &n) { ++n; }

int main()
{
    int x = 5;

    auto bound = std::bind(increment, std::ref(x));
    bound();
    std::cout << x << "\n";   // 6 -- x itself was modified
}

Higher-Order Functions and the Algorithms Library

A higher-order function is one that takes another function as a parameter, returns one, or both. <algorithm> and <numeric> are built almost entirely around this idea: rather than writing a raw for loop for every transformation, filter, or reduction, you hand the intent to a named algorithm and supply only the specific comparison or transformation as a callable.

std::sort takes a comparator lambda instead of assuming a fixed order; std::count_if and std::accumulate are the classic higher-order 'filter' and 'reduce' operations, expressed with a predicate and a binary operation respectively; std::transform is 'map', applying a callable to every element and writing the results into a destination range.

nums starts as {5, 2, 8, 1, 9, 3}.

std::sort with a > comparator leaves nums as {9, 8, 5, 3, 2, 1}.

count_if with n > 3 counts 9, 8, 5 -- count = 3.

accumulate starting from 0 sums all six elements -- sum = 28.

transform squares every element of the sorted nums into squares.

This style scales: because each algorithm takes the varying logic as a parameter, the same std::sort, std::count_if, or std::transform works unchanged for ints, strings, custom structs, or anything else, as long as you supply the right callable. The loop body never has to be rewritten -- only the callable passed to it changes.

What is a closure type, and how does a lambda expression relate to it?

A closure type is the unique, unnamed class the compiler generates for a lambda expression -- it has member variables for each captured variable and an operator() containing the lambda's body. Writing a lambda is really just inline shorthand for defining an instance of this compiler-generated functor class.

Why is it dangerous for a lambda to capture a local variable by reference if the lambda might be stored and called later (e.g. via std::function or returned from a function)?

A reference capture stores a reference to the original variable, not a copy. If the lambda outlives the scope that variable belongs to (the function returns, the loop iteration ends, etc.), the reference becomes dangling, and calling the lambda later reads or writes memory that's already been reused or destroyed -- undefined behavior. Capturing by value avoids this by storing an independent copy inside the lambda's closure.

Why does std::function typically cost more at runtime than passing a callable as a template parameter?

std::function type-erases whatever callable it holds, which usually requires a heap allocation (for callables too large for its small-buffer optimization) and an indirect call through a stored function pointer or vtable-like mechanism on every invocation. A template parameter is resolved and typically inlined at compile time, with no allocation and no indirection, so it's faster whenever the callable's type is known statically and doesn't need to change at runtime.

What problem does std::ref solve when used with std::bind or std::thread?

std::bind and std::thread copy their arguments by value into internal storage by default, even if the target function expects a reference parameter -- so the function would end up modifying an internal copy rather than the caller's original object. std::ref(x) wraps x in a std::reference_wrapper, which is copied cheaply but implicitly converts back to a real reference when the call happens, so the original object is the one actually modified.

In one sentence, what does std::invoke provide that calling a callable directly with f(args...) does not?

std::invoke(f, args...) provides one uniform calling syntax that works whether f is a free function, a lambda, a functor, a pointer to a member function, or a pointer to a data member -- the last two of which need different syntax ((obj.*ptr)(args) or obj.*ptr) when called directly -- which matters most inside generic template code that doesn't know in advance which kind of callable it was handed.

Write a C++ program that defines a stateful functor class RunningAverage with an operator()(double x) that returns the running average of every value passed to it so far, and demonstrates it on a sequence of values.

RunningAverage keeps total and count as member variables that persist across calls, so each call to operator() updates both and returns their current ratio. Feeding it 10, 20, and 30 in sequence prints 10, then 15, then 20 -- the running average after each new value, which a stateless function could not do since it has no memory of previous calls.

What does this program print, and why does the second call to make_adder produce an independent counter from the first?

It prints 123 then 1. Each call to make_adder() creates its own local count and returns a new lambda that captured count by value into its own closure instance, so a and b are two entirely separate objects with independent copies of count. Calling a() three times advances a's private copy to 1, 2, 3; b() then starts fresh from its own copy, printing 1. The mutable keyword is required because ++count modifies the captured-by-value member, which operator() would otherwise treat as const.

Write a C++ program that uses std::transform and a lambda to convert a std::vector<std::string> of words into a new vector containing the length of each word, without writing a manual for loop.

std::transform applies the lambda [](const std::string &w) { return w.size(); } to every element of words and writes each result into the corresponding position of lengths, which is the 'map' pattern from functional programming expressed with the standard library instead of a hand-written loop. For {"cat", "elephant", "dog", "hippopotamus"} it prints 3 8 3 12.

What does this program print, and what would change if std::ref(x) were removed from the std::bind call?

It prints 28. std::ref(x) makes std::bind store a reference_wrapper around x rather than a copy, so each call to op() invokes double_it on the real x -- 7 becomes 14 after the first call and 28 after the second. If std::ref(x) were removed, std::bind would copy x by value into its internal storage; double_it would then double that internal copy on each call, and the program would print 7, since the original x outside op would never be touched.

#include <algorithm>
#include <numeric>
#include <vector>
#include <iostream>

int main()
{
    std::vector<int> nums{5, 2, 8, 1, 9, 3};

    // Sort descending using a lambda comparator
    std::sort(nums.begin(), nums.end(), [](int a, int b) { return a > b; });

    // Count how many are greater than 3
    int count = std::count_if(nums.begin(), nums.end(), [](int n) { return n > 3; });

    // Sum all elements with std::accumulate (a fold/reduce)
    int sum = std::accumulate(nums.begin(), nums.end(), 0);

    // Transform into a new vector of squares
    std::vector<int> squares(nums.size());
    std::transform(nums.begin(), nums.end(), squares.begin(),
                    [](int n) { return n * n; });

    std::cout << "count>3: " << count << ", sum: " << sum << "\n";
    for (int s : squares) std::cout << s << " ";
    std::cout << "\n";
}
#include <iostream>
#include <functional>

std::function<int()> make_adder()
{
    int count = 0;
    return [count]() mutable { return ++count; };
}

int main()
{
    auto a = make_adder();
    auto b = make_adder();

    std::cout << a() << a() << a() << "\n";
    std::cout << b() << "\n";
}
#include <functional>
#include <iostream>

void double_it(int &n) { n *= 2; }

int main()
{
    int x = 7;
    auto op = std::bind(double_it, std::ref(x));
    op();
    op();
    std::cout << x << "\n";
}