6

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?)?

xtofl
  • 40,723
  • 12
  • 105
  • 192
  • 4
    I don't see how this is a problem. Forcing the function to take a reference achieves nothing because it can always just make a copy in the function body. You could even make a trivial wrapper: `handler = [](const Foo &ref) { pass_by_value(ref); };`. – melpomene Sep 15 '17 at 11:25
  • 3
    I'm more interested in *why* you're asking this question? Is it just curiosity? Or do you have an actual underlying problem that prompted this question? Perhaps you should ask a question about *that* problem instead? – Some programmer dude Sep 15 '17 at 11:30
  • 2
    @melpomene: one would expect that because the signatures differ, this should not compile. For example, if you've used simple function pointers here, it would have not compiled. – geza Sep 15 '17 at 11:32
  • 2
    As for your question, this is because the `std::function` object have its own `operator()` function, which then calls (in your case) the lambda. The `std::function` function-call operator is called with the argument being a constant reference, but when it calls the lambda the it is called by value. This is nothing wrong or unexpected, calling a function taking an argument by value, passing a reference, always have worked that way. The `std::function` object doesn't know, doesn't *need* to know, the actual arguments of the thing it calls, only that it *can* be called (which is checked). – Some programmer dude Sep 15 '17 at 11:37
  • That's the point of `std::function`: it adjusts the arguments that you pass to match the arguments required by the wrapped function and it adjusts the value that the wrapped function returns so that it matches the return type of the `std::function` object. If that's a problem then you're using the wrong tool -- `std::function` isn't the solution. – Pete Becker Sep 15 '17 at 12:10
  • @geza -- "because the signatures differ, this should not compile" -- no, it's perfectly correct. That's why `std::function` exists: to make the appropriate type adjustments. If you don't want the type adjustments don't use `std::function`. – Pete Becker Sep 15 '17 at 12:12
  • 1
    @PeteBecker: I don't think that `std::function` exists because of this. It exists because I can call a function later, or I can call a function at a different place uniformly. This conversion functionality is just an implementation detail, and it would be completely fine (in my opinion) that this example doesn't complile. But I'm not saying that it would be good (actually I think that this auto-conversion is a good thing). (I just added my first comment to make you understand why the question supposedly was asked.) – geza Sep 15 '17 at 12:15
  • @geza -- no, the conversion functionality is fundamental. `std::function` is called a [polymorphic function wrapper](http://en.cppreference.com/w/cpp/utility/functional/function) **because** that's what it does. If you only need to be able to call a function later you can simply pass that function (in whatever form) as a template argument (see, for example, any of the standard algorithms that take function objects). `std::function` adds a layer of indirection so that it can adjust argument and return types. – Pete Becker Sep 15 '17 at 12:19
  • @PeteBecker: "in whatever form as a template argument". That's the problem. `std::function` is used a lot of places, where it isn't passed in a as template argument. It blurs the origin of the function, and can store it to be called later. For me, this is the main reason it exists. If you **only** need this conversion functionality, why would you use `std::function`? You could just call the initializer which was used to initialize your `std::function`, couldn't you? The compiler will adjust types without `std::function`. – geza Sep 15 '17 at 12:25
  • @geza -- yes, that's often how `std::function` objects are used, and that's an important part of the design. It's not the only part, and, again, it's called **polymorphic** because it does type adjustments, letting you treat a wrapped function object as if it had a **different** argument list. If the wrapped object takes an argument of type `xxx` which can be constructed from an `int` and I call it with an argument of type `yyy` which has a conversion to `int`, the compiler will refuse to do it: too many user-defined conversions. Inside an `std::function` it's okay. – Pete Becker Sep 15 '17 at 12:39
  • @Someprogrammerdude I wanted to avoid client code installing callbacks that create copies by accident, hence my interest in some way of emitting a warning – xtofl Sep 15 '17 at 12:39
  • @PeteBecker: okay, we just tend to differ how important/fundamental this auto-conversion is :), but I see your point, thanks for the XXX/int/YYY example! – geza Sep 15 '17 at 12:57
  • @xtofl: maybe you can create a wrapper class around `std::function`, and add a `operator=` which can cause compiler error if types doesn't match (this can be achieved for example by creating a function pointer for both types, and assigning them) – geza Sep 15 '17 at 13:00

1 Answers1

5

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.

Mark
  • 1,016
  • 6
  • 10