Concepts / Function Objects in C++

Function Objects in C++

An object of a class that defines operator() can be called too.

  • Programming

A Variable That Accepts Many Kinds of Callables

In C++ several different things can be called with the call operator (). A plain function can be called. A lambda can be called. An object of a class that defines operator() can be called too. Each of these is written with its own syntax, so the three look quite different on the page. Yet a programmer often wants one variable that can hold any of them and be called in the same way. The standard library provides exactly this kind of type under the name function, written std::function in full.

What do you think happens?

Suppose a std::function is declared to take a double argument, and you try to assign a function that takes an int argument to it. Will the assignment be accepted?

  • Yes, because int and double are both numbers

  • No, because the argument types do not match

Reveal answer
Answer:

No, because the argument types do not match

The declared type fixes the argument type as double. A callable whose argument type is int has a different call signature, so the assignment is rejected. The rest of this article explains the rule behind this result.

Reading the Declaration Piece by Piece

A std::function is specified with a specific return type and a specific argument type. The source gives the example function<int(double)>. Read it as a pattern: the type inside the angle brackets begins with the return type, followed by the argument type in parentheses. In function<int(double)> the return type is int and the argument type is double. This fixed pair is written into the declared type itself, so the variable's type says what kind of callable it is willing to hold.

std::functiontype that holds a callableintreturn typedoubleargument type
Which token of std::function<int(double)> sets the return type, and which token sets the argument type?

The declaration can also create the variable and store a callable in it at the same moment. The source gives the form function<int(double)> fct {f1}; which declares the variable fct and stores f1 in it during the declaration. The variable can later be given a different callable of the same signature by assignment.

cpp
Notes on the code
  • The declaration fixes the return type int and the argument type double
  • Initialization stores f1 in fct at the moment of declaration
  • Assignment later replaces the stored callable, but only with one that fits the same signature

What a Stored Callable Has to Match

The source makes a central point here. Ordinary functions, lambdas, and function objects can all be stored in a std::function when their signatures match. The variable does not care how the callable was written. It checks only the call signature: how many arguments it takes, what their types are, and what type of result it produces. This makes the declared type a contract about arguments and return values, not about the internal form of the callable.

std::function<int(double)>one declared signatureOrdinary functionplain named functionLambdacallable written inlineFunction objectobject defining operator()can hold if signature matchescan hold if signature matchescan hold if signature matches
How can a plain function, a lambda, and a function object all be stored in the same std::function variable?

The question to carry forward is this: what property of the declared type decides whether a given assignment is accepted? The answer is the signature. A callable is accepted when its argument types and return type fit the pair declared inside the angle brackets.

Tracing the Assignments in the Source Example

The source shows a declaration and then a series of assignments to the same variable. Read the code first, noting which assignments are expected to succeed and which one is marked as an error. In the first two assignments, one callable is a lambda and the other is a named function. Both are accepted, even though they were written in different ways.

Worked example

Why the third assignment fails

fct was declared as std::function<int(double)>. Assigning f2 to fct is rejected. Why?

  1. 1
    Identify the declared signature

    fct was declared with argument type double and return type int.

  2. 2
    Identify the callable's argument type

    f2 has the argument type int, which differs from double.

  3. 3
    Compare the signatures

    The argument types do not match, so f2 does not fit the contract of fct.

  4. 4
    Read the reason from the source

    The source's comment states directly that the argument type is incorrect, and the assignment is flagged as an error.

Result

The assignment fct = f2; is rejected at compile time because the argument type of f2 does not match the declared argument type double.

AssignmentCallable kindSignature matches int(double)?Result
First assignmentLambda or named function (per the source)YesAccepted
Second assignmentThe other of lambda or named functionYesAccepted
fct = f2;Function whose argument type is intNoRejected

Which assignments succeed when fct is declared as std::function<int(double)>

Function Objects Compared With Plain Functions and Lambdas

A function object is a class object that defines operator(), so that the object itself can be called with the call operator. It is one of the three kinds of callable the source names. The three can be told apart by how each is written. A plain function is declared with a name and a body. A lambda is a callable written inline in the code. A function object is an object whose class defines operator(). All three can be stored in a std::function whose signature they match.

cpp
Output
result is 5
Notes on the code
  • Doubler is a class whose objects can be called because it defines operator()
  • Doubler{} creates a function object that is stored in fct
  • The call fct(2.5) goes through the stored callable's operator()
Plain functiondeclared with a nameLambdacallable written inlineFunction objectobject whose class definesoperator()
What feature separates each kind of callable from the other two?

Mistakes With Signatures

  • Common mistake

    Assuming that a std::function accepts any callable with the right number of arguments

    What it looks like

    std::function<int(double)> fct; then assigning a function that takes an int argument

    Why it fails

    The source states that the argument type must match. An int parameter where double is declared cannot be assigned.

    Do this instead

    Make the callable's argument and return types match the signature written in the declaration, or declare the std::function with the type you actually need.

  • Common mistake

    Thinking that a lambda and a named function must be written the same way to be stored

    What it looks like

    Expecting a lambda to be rejected because it is not a named function

    Why it fails

    The source says the variable does not care how the callable was written. Only the call signature is checked.

    Do this instead

    Check the signature, not the form. Both a lambda and a named function can be stored when their signatures match.

  • Common mistake

    Confusing the return type with the argument type when reading the declaration

    What it looks like
    Reading std::function<int(double)> as taking an int argument
    Why it fails

    In function<int(double)> the return type is int and the argument type is double.

    Do this instead

    Read the first type as the return type and the type in parentheses as the argument type.

Practice Reading Signatures

MEDIUM

A variable is declared as std::function<int(double)> fct. Which of the following callables can be assigned to it: a named function that returns int and takes a double, a lambda that returns int and takes a double, or a function that returns double and takes a double? Explain your answer using the signature rule.

Hints
  • Compare each callable's return type and argument type with the pair declared inside the angle brackets.
  • The form of the callable does not matter; only the signature does.

Key Takeaways

  • std::function is a type that can hold any object you can invoke using the call operator, including ordinary functions, lambdas, and function objects.
  • A std::function is declared with a specific return type and a specific argument type, such as function<int(double)>, and this pair is the contract that every stored callable must satisfy.
  • A callable is accepted when its signature matches the declared signature; its written form, whether a named function, a lambda, or a function object, does not matter.
  • An assignment fails when the argument type or return type differs from the declared one, as in the source's example where an int argument is assigned to a double-typed declaration.
  • A function object is a class object whose class defines operator(), and it is distinct from a plain named function and from an inline lambda.

Related concepts

More concepts starting with F