Search code examples
c++covariancecoercionfunction-object

Why can I pass a value-accepting callable to a reference-accepting std::function?


When I declare a variable function<void(const Foo&)>, the compiler still lets me assign a lambda that accepts a value:

function<void(const Foo&)> handler;
handler = [](Foo f){};

(cfr. also http://cpp.sh/5dsp)

So when the handler is called, a copy will be made. What section of the standard allows this? Is there a way I can flag the client code that this will be a problem (some static_assert of sorts?)?


Solution

  • Per [func.wrap.func.con]

    std::function<R(ArgTypes...)>
    

    has a

    template<class F> function& operator=(F&& f);
    

    with the following remark:

    This assignment operator shall not participate in overload resolution unless decay_t<F> is Lvalue-Callable for argument types ArgTypes... and return type R.

    where

    A callable type F is Lvalue-Callable for argument types ArgTypes and return type R if the expression INVOKE<R>(declval<F&>(), declval<ArgTypes>()...), considered as an unevaluated operand, is well formed.

    So you can assign to an std::function<R(ArgTypes...)> any function callable with ArgTypes... and returning something implicitly convertible to R.

    I don't see how to prevent this short of wrapping an std::function in something more limited.