How should I define a std::function variable with default arguments?

wibek picture wibek · Apr 7, 2014 · Viewed 9.8k times · Source

To set a std::function variable to a lambda function with default argument I can use auto as in:

auto foo = [](int x = 10){cout << x << endl;};
foo();

This will print 10.

But I want the foo variable to reside in a struct. In a struct I cannot use auto.

struct Bar
{
    auto foo = [](int x = 10}(cout << x << endl}; //error: non-static data member declared ‘auto’
};
Bar bar;
bar.foo();

Replacing auto with std::function

struct Bar
{
    std::function<void(int x = 10)> foo = [](int x = 10}(cout << x << endl}; //error: default arguments are only permitted for function parameters
};
Bar bar;
bar.foo();

or

struct Bar
{
    std::function<void(int)> foo = [](int x = 10}(cout << x << endl};
};
Bar bar;
bar.foo(); //error: no match for call to ‘(std::function<void(int)>) ()’

Without the struct and replacing auto for std::function:

std::function<void(int x)> foo = [](int x = 10){cout << x << endl;};
foo(); //error: no match for call to ‘(std::function<void(int)>) ()’

So how should I declare foo?

Answer

Nevin picture Nevin · Apr 7, 2014

The signature in std::function is based on how you plan to call it and not on how you construct/assign it. Since you want to call it two different ways, you'll need to store to different std::function objects, as in:

struct Call
{
    template<typename F>
    explicit Call(F f) : zero_(f), one_(std::move(f)) {}

    void operator()() { zero_(); }
    void operator()(int i) { one_(i); }

    std::function<void()>    zero_;
    std::function<void(int)> one_;
};

Alternatively, you can do the type erasure yourself (what std::function does behind the scenes) to only store the lambda once, as in:

class TECall
{
    struct Concept
    {   
        Concept() = default;
        Concept(Concept const&) = default;
        virtual ~Concept() = default;

        virtual Concept* clone() const = 0;

        virtual void operator()() = 0;
        virtual void operator()(int) = 0;
    };  

    template<typename T>
    struct Model final : Concept
    {   
        explicit Model(T t) : data(std::move(t)) {}
        Model* clone() const override { return new Model(*this); }

        void operator()() override { data(); }
        void operator()(int i) override { data(i); }

        T data;
    };  

    std::unique_ptr<Concept> object;

public:
    template<typename F>
    TECall(F f) : object(new Model<F>(std::move(f))) {}

    TECall(TECall const& that) : object(that.object ? that.object->clone() : nullptr) {}
    TECall(TECall&& that) = default;
    TECall& operator=(TECall that) { object = std::move(that.object); return *this; }

    void operator()() { (*object)(); }
    void operator()(int i) { (*object)(i); }
};