20

A while ago I used std::function pretty much like this:

std::function<void(int)> func = [](int i) -> int { return i; };

Basically, I did this because I wanted to store different function objects in a std::function, but I didn't want to restrict the return types of these functions. Since this seemed to work, I went with it. But I'm not convinced that it is safe to use, and I haven't been able to find any documentation on it. Does anyone know whether this usage is legitimate? Or more generally, what the rules are for the object which can safely be assigned to a std::function?

Edit

For clarification, the issue I'm concerned with is that the lambda function returns an int, while func is declared with return type void. I'm not sure if this is OK, especially once a call to func() is made.

ildjarn
  • 62,044
  • 9
  • 127
  • 211
Ken Wayne VanderLinde
  • 18,915
  • 3
  • 47
  • 72

3 Answers3

21

Your code has undefined behavior. It may or may not work as you expect. The reason it has undefined behavior is because of 20.8.11.2.1 [func.wrap.func.con]/p7:

Requires: F shall be CopyConstructible. f shall be Callable (20.8.11.2) for argument types ArgTypes and return type R.

For f to be Callable for return type R, f must return something implicitly convertible to the return type of the std::function (void in your case). And int is not implicitly convertible to void.

I would expect your code to work on most implementations. However on at least one implementation (libc++), it fails to compile:

test.cpp:7:30: error: no viable conversion from 'int (int)' to 'std::function<void (int)>'
    std::function<void(int)> ff = f;
                             ^    ~

Ironically the rationale for this behavior stems from another SO question.

The other question presented a problem with std::function usage. The solution to that problem involved having the implementation enforce the Requires: clause at compile time. In contrast, the solution to this question's problem is forbidding the implementation from enforcing the Requires: clause.

Community
  • 1
  • 1
Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • @LucDanton: I didn't look at the `boost::function` documentation. I don't believe it is relevant for this question. I only looked at N3290 which is the paper voted on as the C++11 standard. The other SO question has to do with having the implementation enforce the *Requires:* clause at compile time. Such enforcement is not required, but is conforming. – Howard Hinnant Feb 18 '12 at 23:59
  • 1
    No. The other question presented a problem with `std::function` usage. The solution to that problem involved having the implementation enforce the *Requires:* clause at compile time. In contrast, the solution to this question's problem is **forbidding** the implementation from enforcing the *Requires:* clause. – Howard Hinnant Feb 19 '12 at 01:29
  • From a theoretical point of view, this is relatively easy to support. Either provide a partial specialization on void return types and exclude the `is_convertible<..., _Rp>` check in the `__callable` trait + ignore the return value in `operator()`, or (probably better), add another level of indirection to the convertible check, specialized on void return type, and call the wrapped object through a `maybe_swallow` function like [here](http://ideone.com/f75VI). As it's a reference implementation only, I was lazy and only supported function pointer. :) – Xeo Feb 20 '12 at 00:18
  • However, one has to note the caveat of ambiguity when a function is overloaded on `std::function`, and both overloads are the same except the return type of `std::function`. See the comment in the provided link. – Xeo Feb 20 '12 at 00:20
  • Howard, is there something wrong [with the argument that any invokation or useful creation of `std::function` is illegal](http://stackoverflow.com/q/25395605/1774667)? – Yakk - Adam Nevraumont Sep 09 '15 at 12:06
  • Aha! I think this is the new wording: "A function object f of type F is Callable for argument types T1, T2, ..., TN in ArgTypes and a return type R, if, given lvalues t1, t2, ..., tN of types T1, T2, ..., TN, respectively, INVOKE (f, t1, t2, ..., tN) is well formed (20.7.2) and, if R is not void, convertible to R." So it appears that this void problem (and mine above) has disappeared in the latest standard? – Yakk - Adam Nevraumont Sep 09 '15 at 12:19
  • 2
    @Yakk: You are correct. It would not be a bad idea for me to update this answer to detail when and how the rules have evolved. – Howard Hinnant Sep 09 '15 at 14:37
  • Is it possible to get a compiler warning when doing such an assignment? – marczellm Jun 26 '19 at 09:18
1

Your use case is well-defined according to the standard.

You are constructing a std::function from a callable object[1]

§20.8.11.2.1/7:

template<class F> function(F f);

Requires: F shall be CopyConstructible. f shall be Callable (20.8.11.2) for argument types ArgTypes and return type R.

So is your f callable?

§20.8.11.2/2 says:

A callable object f of type F is Callable for argument types ArgTypes and return type R if the expres- sion INVOKE (f, declval<ArgTypes>()..., R), considered as an unevaluated operand (Clause 5), is well formed (20.8.2).

And the definition of INVOKE says:

§20.8.2

  1. Define INVOKE (f, t1, t2, ..., tN) as follows: ... stuff dealing with member function/var pointers ... — f(t1, t2, ..., tN) in all other cases.

  2. Define INVOKE (f, t1, t2, ..., tN, R) as INVOKE (f, t1, t2, ..., tN) implicitly converted to R.

And since any type can be implicitly converted to void, your code should be fine with a standards-conforming compiler. As pointed out by litb below, there isn't an implicit conversion to void so this isn't well defined.

[1]: I think the lambda counts as a callable object here, although I don't have a reference for that. Your lambda could also be used as a function pointer as it captures no context

je4d
  • 7,628
  • 32
  • 46
  • 1
    There is no implicit conversion to void. – Johannes Schaub - litb Feb 18 '12 at 15:47
  • @JohannesSchaub-litb Then is the original code wrong, or am I just using the wrong section of the standard to justify it? – je4d Feb 18 '12 at 16:02
  • @je4d: Wow, thanks for the references. But it looks like Johannes is right - the only thing stopping this from being correct is that we can't implicitly convert to void. Looks like I'll have to wrap the function objects in a void returning function. – Ken Wayne VanderLinde Feb 18 '12 at 17:55
0

This looks like it may be ok for anonymous functions.

Quote from http://www.alorelang.org/release/0.5/doc/std_function.html (this is not from the C++ standard library, however it looks like they are using something similar in there bindings down to C++)

Function objects can only be created with a function definition, an anonymous function expression or by accessing a bound method using the dot (.) operator.

Another way this could possibly be done is by storing the function pointer in auto as seen here: http://en.wikipedia.org/wiki/Anonymous_function (C++ section)

NothingMore
  • 1,211
  • 9
  • 19
  • Thanks for the answer. But the question was really about the mismatch in return types. My lambda returns an `int`, but it's stored in a `std::function` which has `void` as the return type. – Ken Wayne VanderLinde Feb 18 '12 at 09:15