Reputation: 261
The following code is supposedly illegal in C++14 but legal in C++17:
#include <functional>
int main()
{
int x = 1729;
std::function<void (int&)> f(
[](int& r) { return ++r; });
f(x);
}
Don't bother testing it, you'll get inconsistent results making it difficult to suss whether it's a bug or intentional behavior. However, comparing two drafts (N4140 vs N4527, both can be found on github.com/cplusplus/draft), there's one significant difference in [func.wrap.func.inv]. Paragraph 2:
Returns: Nothing if R is void, otherwise the return value of INVOKE (f, std::forward(args)..., R).
The above was removed between drafts. The implication is that the return value of the lambda is now silently discarded. This seems like a misfeature. Can anyone explain the reasoning?
Upvotes: 26
Views: 1004
Reputation: 275800
There was a ridiculous defect in the standard about std::function<void(Args...)>
. By the wording of the standard, no (non-trivial)1 use of std::function<void(Args...)>
was legal, because nothing can be "implicitly converted to" void
(not even void
).
void foo() {} std::function<void()> f = foo;
was not legal in C++14. Oops.
Some compilers took the bad wording that made std::function<void(Args...)>
completely useless, and applied the logic only to passed-in callables where the return value was not void
. Then they concluded it was illegal to pass a function returning int
to std::function<void(Args...)>
(or any other non-void
type). They did not take it to the logical end and ban functions returning void
as well (the std::function
requirements make no special case for signatures that match exactly: the same logic applies.)
Other compilers just ignored the bad wording in the void
return type case.
The defect was basically that the return type of the invocation expression has to be implicitly convertible-to the return type of the std::function
's signature (see link above for more details). And under the standard, void
cannot be implicitly converted to void
2.
So the defect was resolved. std::function<void(Args...)>
now accepts anything that can be invoked with Args...
, and discards the return value, as many existing compilers implemented. I presume this is because (A) the restriction was not ever intended by the language designers, or (B) having a way for a std::function
that discards return values was desired.
std::function
has never required exact match of arguments or return values, just compatibility. If the incoming arguments can implicitly convert-from the signature arguments, and the return type can implicitly convert-to the return type, it was happy.
And a function of type int(int&)
is, under many intuitive definitions, compatible with the signature void(int&)
, in that you can run it in a "void context".
1 Basically, anything that makes operator()
legal to call was not allowed. You can create it, you can destroy it, you can test it (and know it is empty). You cannot give it a function, even one that matches its signature exactly, or a function object or lambda. Ridiculous.
2 For void
to be impliclty converted to void
under the standard, it requires that the statement void x = blah;
, where blah
is an expression of type void, be valid; that statement is not valid, as you cannot create a variable of type void
.
Upvotes: 21