73

You know, we can wrap or store a lambda function to a std::function:

#include <iostream>
#include <functional>
int main()
{
    std::function<float (float, float)> add = [](float a, float b)
    //            ^^^^^^^^^^^^^^^^^^^^
    {
        return a + b;
    };

    std::cout << add(1, 2) << std::endl;
}

My question is around std::function, as you can see it is a template class but it can accept any kind of function signature.

For example float (float, float) in this form return_value (first_arg, second_arg).

What's the structure of std::function and how does it accept a function signature like x(y,z) and how it works with it? Is float (float, float) a new valid expression in C++?

masoud
  • 55,379
  • 16
  • 141
  • 208

1 Answers1

123

It uses some type erasure technique.

One possibility is to use mix subtype polymorphism with templates. Here's a simplified version, just to give a feel for the overall structure:

template <typename T>
struct function;

template <typename Result, typename... Args>
struct function<Result(Args...)> {
private:
    // this is the bit that will erase the actual type
    struct concept {
        virtual Result operator()(Args...) const = 0;
    };

    // this template provides us derived classes from `concept`
    // that can store and invoke op() for any type
    template <typename T>
    struct model : concept {
        template <typename U>
        model(U&& u) : t(std::forward<U>(u)) {}

        Result operator()(Args... a) const override {
            t(std::forward<Args>(a)...);
        }

        T t;
    };

    // this is the actual storage
    // note how the `model<?>` type is not used here    
    std::unique_ptr<concept> fn;

public:
    // construct a `model<T>`, but store it as a pointer to `concept`
    // this is where the erasure "happens"
    template <typename T,
        typename=typename std::enable_if<
            std::is_convertible<
                decltype( t(std::declval<Args>()...) ),
                Result
            >::value
        >::type>
    function(T&& t)
    : fn(new model<typename std::decay<T>::type>(std::forward<T>(t))) {}

    // do the virtual call    
    Result operator()(Args... args) const {
        return (*fn)(std::forward<Args>(args)...);
    }
};

(Note that I overlooked several things for the sake of simplicity: it cannot be copied, and maybe other problems; don't use this code in real code)

Community
  • 1
  • 1
R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
  • 8
    +1. It's highly regrettable that I cannot upvote more times for this answer. – xmllmx Feb 18 '13 at 14:44
  • @Martinho, where is the definition of 'Unqualified'? – xmllmx Feb 18 '13 at 14:52
  • 2
    @xmllmx It's an alias template that removes all qualifiers (const, volatile, &, and &&) from a type. Same as `Bare` here: http://flamingdangerzone.com/cxx11/2012/05/29/type-traits-galore.html#bare_types (I have since changed my mind and found Unqualified to be a better name :) – R. Martinho Fernandes Feb 18 '13 at 14:56
  • 2
    @xmllmx, actually, scratch that. It should be `std::decay` instead (it simulates pass-by-value semantics, which is what I want: I want to store by value). They are similar, but not quite the same here. – R. Martinho Fernandes Feb 18 '13 at 15:10
  • @Martinho, the argument name 'args' is lacked in function::operator (). Please add it to make the code complete. – xmllmx Feb 18 '13 at 15:20
  • @R.MartinhoFernandes I couldn't resist fixing the constructor. :) Free free to rollback, probably too complex for this question. – Yakk - Adam Nevraumont Feb 18 '13 at 18:46
  • @Yakk that's ok. Note my edit, btw: if you use declval, you don't need to forward; declval is pretty much forward without a value to forward: its return type already has the right value category. – R. Martinho Fernandes Feb 19 '13 at 08:23
  • How is this simplified? I.e. what can it not do that it should do? – rubenvb Apr 24 '13 at 09:59
  • @R.MartinhoFernandes Should the code, instead of `t(std::forward(a)...);` be `return t(std::forward(a)...);` ? – AndyG Aug 21 '15 at 15:49
  • How would this class be used, using it like std::function results in a compile error? – paulm Jan 21 '16 at 09:00
  • @paulm I don't know. I never actually used this class. I only wrote it for explanatory purposes. See the note at the end. – R. Martinho Fernandes Jan 21 '16 at 10:08
  • @R.MartinhoFernandes Isn't `concept` actually missing a virtual destructor? Thinking on functors with non-trivial destructors. Excellent answer, by the way. – alecov Jul 06 '17 at 18:28
  • @R.MartinhoFernandes - I assume you’re missing `return` from `model`’s `operator()`? – Dan Nissenbaum Dec 15 '18 at 09:36