27

All quotes are from N3797.

4/3 [conv]

An expression e can be implicitly converted to a type T if and only if the declaration T t=e; is well-formed, for some invented temporary variable t

This implies no expression can be implicitly converted to void, as void t=e is illegal for all expressions e. This is even true if e is an expression of type void, such as void(3).

So an expression of type void cannot be implicitly converted to void.

Which leads us to:

20.9.2/2 Requirements [func.require]

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

In short, INVOKE(f, t1, t2, ..., tN, R) is never valid when R is void, as nothing (including void) can be implicitly converted to void.

As a result of this, all std::function<void(Args...)> have the property !*this and thus cannot be called, as the only constructors that do not have !*this as a postcondition (or do not copy such state from another function of the same type) require Callable of one of the parameters.

20.9.11.2/7 Class template function [func.wrap.func]

Requires: F shall be CopyConstructible . f shall be Callable ( 20.9.11.2 ) for argument types ArgTypes and return type R . The copy constructor and destructor of A shall not throw exceptions.

20.9.11.2/2 Class template function [func.wrap.func]

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

As demonstrated above, there are no Callable expressions for std::function<void(Args...)>.

If somehow such a std::function<void(Args...)> where found, invoking operator() would be ill formed:

invocation [func.wrap.func.inv]

Effects: INVOKE (f, std::forward(args)..., R) ( 20.9.2 ), where f is the target ob- ject ( 20.9.1 ) of *this .

as INVOKE(f, std::forward<ArgTypes>(args)..., void) is ill formed for all arguments and f.

Is this line of reasoning sound?

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Sounds like a defect in the standard. – T.C. Aug 20 '14 at 03:01
  • 1
    Interestingly `is_convertible::value` is `true` – Praetorian Aug 20 '14 at 05:38
  • 1
    @Praetorian Yes, that type trait is specified ([meta.rel]/p4) in a rather convoluted manner to "give well defined results for reference types, void types, array types, and function types." – T.C. Aug 20 '14 at 05:59
  • @Potatoswatter No, `declval` uses `add_rvalue_reference`, which is special-cased for `void`. – T.C. Oct 30 '15 at 20:18

1 Answers1

16

Yes, your analysis is correct; I came to the same conclusion here.

According to Daniel Kruegler, this issue should appear on the library defect list subsequent to the next mailing:

A corresponding library issue has already been submitted, but is not yet visible in the issue list.

Hopefully once that becomes visible we'll also have a conclusive answer to whether it is allowable to construct a std::function with signature returning void passing a callable with signature returning non-void (Using `std::function<void(...)>` to call non-void function).


Update: this was entered as LWG 2420, which was resolved in favor of special-casing void return type to static_cast the result of the invoked function to void. This means that a callable returning non-void can be the target of a std::function<void(...)>. LWG2420 was applied as a post-publication correction to C++14; meanwhile, all compilers I'm aware of effectively apply this behavior as an extension in C++11 mode.

Community
  • 1
  • 1
ecatmur
  • 152,476
  • 27
  • 293
  • 366