简体   繁体   中英

Why does operator() change for std::function in C++17?

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?

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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM