std::function
's Callable definition is brokenSection: 22.10.17.3 [func.wrap.func] Status: C++17 Submitter: Daniel Krügler Opened: 2014-06-03 Last modified: 2017-07-30
Priority: 2
View all other issues in [func.wrap.func].
View all issues with C++17 status.
Discussion:
The existing definition of std::function
's Callable requirements provided in 22.10.17.3 [func.wrap.func]
p2,
A callable object
f
of typeF
is Callable for argument typesArgTypes
and return typeR
if the expressionINVOKE(f, declval<ArgTypes>()..., R)
, considered as an unevaluated operand (Clause 5), is well formed (20.9.2).
is defective in several aspects:
The wording can be read to be defined in terms of callable objects, not of callable types.
Contrary to that, 22.10.17.3.6 [func.wrap.func.targ] p2 speaks of "T
shall be a type that is Callable
(20.9.11.2) for parameter types ArgTypes
and return type R
."
The required value category of the callable object during the call expression (lvalue or rvalue) strongly depends on
an interpretation of the expression f
and therefore needs to be specified unambiguously.
The intention of original proposal
(see IIIa. Relaxation of target requirements) was to refer to both types and values ("we say that the function object f
(and its type F
) is Callable […]"), but that mental model is not really deducible from the
existing wording. An improved type-dependence wording would also make the sfinae-conditions specified in 22.10.17.3.2 [func.wrap.func.con]
p8 and p21 ("[…] shall not participate in overload resolution unless f
is Callable (20.9.11.2)
for argument types ArgTypes...
and return type R
.") easier to interpret.
std::function
invokes the call operator of its target via an lvalue. The required value-category is relevant,
because it allows to reflect upon whether an callable object such as
struct RVF { void operator()() const && {} };
would be a feasible target object for std::function<void()>
or not.
A callable
objecttype (22.10.3 [func.def])f
ofF
is Callable for argument typesArgTypes
and return typeR
if the expressionINVOKE(
, considered as an unevaluated operand (Clause 5), is well formed (20.9.2).fdeclval<F&>(), declval<ArgTypes>()..., R)
It seems appealing to move such a general Callable definition to a more "fundamental" place (e.g. as another
paragraph of 22.10.3 [func.def]), but the question arises, whether such a more general concept should impose
the requirement that the call expression is invoked on an lvalue of the callable object — such a
special condition would also conflict with the more general definition of the result_of
trait, which
is defined for either lvalues or rvalues of the callable type Fn
. In this context I would like to point out that
"Lvalue-Callable" is not the one and only Callable requirement in the library. Counter examples are
std::thread
, call_once
, or async
, which depend on "Rvalue-Callable", because they
all act on functor rvalues, see e.g. 32.4.3.3 [thread.thread.constr]:
[…] The new thread of execution executes
INVOKE(DECAY_COPY(std::forward<F>(f)), DECAY_COPY(std::forward<Args>(args))...)
[…]
For every callable object F
, the result of DECAY_COPY
is an rvalue. These implied rvalue function calls are
no artifacts, but had been deliberately voted for by a Committee decision (see LWG 2021, 2011-06-13 comment)
and existing implementations respect these constraints correctly. Just to give an example,
#include <thread> struct LVF { void operator()() & {} }; int main() { LVF lf; std::thread t(lf); t.join(); }
is supposed to be rejected.
The below presented wording changes are suggested to be minimal (still local tostd::function
), but the used approach
would simplify a future (second) conceptualization or any further generalization of Callable requirements of the Library.
[2015-02 Cologne]
Related to N4348. Don't touch with a barge pole.
[2015-09 Telecon]
N4348 not going anywhere, can now touch with or without barge poles
Ville: where is Lvalue-Callable defined?
Jonathan: this is the definition. It's replacing Callable with a new term and defining that. Understand why it's needed, hate the change.
Geoff: punt to an LWG discussion in Kona
[2015-10 Kona]
STL: I like this in general. But we also have an opportunity here to add a precondition. By adding static assertions, we can make implementations better. Accept the PR but reinstate the requirement.
MC: Status Review, to be moved to TR at the next telecon.[2015-10-28 Daniel comments and provides alternative wording]
The wording has been changed as requested by the Kona result. But I would like to provide the following counter-argument for this changed resolution: Currently the following program is accepted by three popular Standard libraries, Visual Studio 2015, gcc 6 libstdc++, and clang 3.8.0 libc++:
#include <functional> #include <iostream> #include <typeinfo> #include "boost/function.hpp" void foo(int) {} int main() { std::function<void(int)> f(foo); std::cout << f.target<double>() << std::endl; boost::function<void(int)> f2(foo); std::cout << f2.target<double>() << std::endl; }
and outputs the implementation-specific result for two null pointer values.
Albeit this code is not conforming, it is probable that similar code exists in the wild. The current boost documentation does not indicate any precondition for calling thetarget
function, so it is natural
that programmers would expect similar specification and behaviour.
Standardizing the suggested change requires a change of all implementations and I don't see any advantage
for the user. With that change previously working code could now cause instantiation errors, I don't see how
this could be considered as an improvement of the status quo. The result value of target
is
always a pointer, so a null-check by the user code is already required, therefore I really see no reason
what kind of problem could result out of the current implementation behaviour, since the implementation
never is required to perform a C cast to some funny type.
Previous resolution [SUPERSEDED]:
This wording is relative to N3936.
Change 22.10.17.3 [func.wrap.func] p2 as indicated:
-2- A callable
objecttype (22.10.3 [func.def])f
ofF
is Lvalue-Callable for argument typesArgTypes
and return typeR
if the expressionINVOKE(
, considered as an unevaluated operand (Clause 5), is well formed (20.9.2).fdeclval<F&>(), declval<ArgTypes>()..., R)Change 22.10.17.3.2 [func.wrap.func.con] p8+p21 as indicated:
template<class F> function(F f); template <class F, class A> function(allocator_arg_t, const A& a, F f);[…]
-8- Remarks: These constructors shall not participate in overload resolution unlessis
fFLvalue-Callable
(20.9.11.2) for argument typesArgTypes...
and return typeR
.[…]
template<class F> function& operator=(F&& f);[…]
-21- Remarks: This assignment operator shall not participate in overload resolution unlessis
declval<typename decay<F>::type&>()decay_t<F>Lvalue-Callable
(20.9.11.2) for argument typesArgTypes...
and return typeR
.Change 22.10.17.3.6 [func.wrap.func.targ] p2 as indicated: [Editorial comment: Instead of adapting the preconditions for the naming change I recommend to strike it completely, because the
target()
functions do not depend on it; the corresponding wording exists since its initial proposal and it seems without any advantage to me. Assume that some template argumentT
is provided, which does not satisfy the requirements: The effect will be that the result is a null pointer value, but that case can happen in other (valid) situations as well. — end comment]template<class T> T* target() noexcept; template<class T> const T* target() const noexcept;-3- Returns: If
-2- Requires:T
shall be a type that isCallable
(20.9.11.2) for parameter typesArgTypes
and return typeR
.target_type() == typeid(T)
a pointer to the stored function target; otherwise a null pointer.
[2015-10, Kona Saturday afternoon]
GR explains the current short-comings. There's no concept in the standard that expresses rvalue member function qualification, and so, e.g. std::function cannot be forbidden from wrapping such functions. TK: Although it wouldn't currently compile.
GR: Implementations won't change as part of this. We're just clearing up the wording.
STL: I like this in general. But we also have an opportunity here to add a precondition. By adding static assertions, we can make implementations better. Accept the PR but reinstate the requirement.
JW: I hate the word "Lvalue-Callable". I don't have a better suggestion, but it'd be terrible to teach. AM: I like the term. I don't like that we need it, but I like it. AM wants the naming not to get in the way with future naming. MC: We'll review it.
TK: Why don't we also add Rvalue-Callable? STL: Because nobody consumes it.
Discussion whether "tentatively ready" or "review". The latter would require one more meeting. EF: We already have implementation convergence. MC: I worry about a two-meeting delay. WEB: All that being said, I'd be slightly more confident with a review since we'll have new wording, but I wouldn't object. MC: We can look at it in a telecon and move it.
STL reads out email to Daniel.
Status Review, to be moved to TR at the next telecon.
[2016-01-31, Daniel comments and suggests less controversive resolution]
It seems that specifically the wording changes for 22.10.17.3.6 [func.wrap.func.targ] p2 prevent this issue from making make progress. Therefore the separate issue LWG 2591 has been created, that focuses solely on this aspect. Furtheron the current P/R of this issue has been adjusted to the minimal possible one, where the term "Callable" has been replaced by the new term "Lvalue-Callable".
Previous resolution II [SUPERSEDED]:
This wording is relative to N4527.
Change 22.10.17.3 [func.wrap.func] p2 as indicated:
-2- A callable
objecttype (22.10.3 [func.def])f
ofF
is Lvalue-Callable for argument typesArgTypes
and return typeR
if the expressionINVOKE(
, considered as an unevaluated operand (Clause 5), is well formed (20.9.2).fdeclval<F&>(), declval<ArgTypes>()..., R)Change 22.10.17.3.2 [func.wrap.func.con] p8+p21 as indicated:
template<class F> function(F f); template <class F, class A> function(allocator_arg_t, const A& a, F f);[…]
-8- Remarks: These constructors shall not participate in overload resolution unlessis
fFLvalue-Callable
(20.9.11.2) for argument typesArgTypes...
and return typeR
.[…]
template<class F> function& operator=(F&& f);[…]
-21- Remarks: This assignment operator shall not participate in overload resolution unlessis
declval<typename decay<F>::type&>()decay_t<F>Lvalue-Callable
(20.9.11.2) for argument typesArgTypes...
and return typeR
.Change 22.10.17.3.6 [func.wrap.func.targ] p2 as indicated:
template<class T> T* target() noexcept; template<class T> const T* target() const noexcept;-2- Remarks: If
-3- Returns: IfT
is a type that is notLvalue-Callable
(20.9.11.2) for parameter typesArgTypes
and return typeR
, the program is ill-formedRequires:.T
shall be a type that isCallable
(20.9.11.2) for parameter typesArgTypes
and return typeR
target_type() == typeid(T)
a pointer to the stored function target; otherwise a null pointer.
[2016-03 Jacksonville]
Move to Ready.Proposed resolution:
This wording is relative to N4567.
Change 22.10.17.3 [func.wrap.func] p2 as indicated:
-2- A callable
objecttype (22.10.3 [func.def])f
ofF
is Lvalue-Callable for argument typesArgTypes
and return typeR
if the expressionINVOKE(
, considered as an unevaluated operand (Clause 5), is well formed (20.9.2).fdeclval<F&>(), declval<ArgTypes>()..., R)
Change 22.10.17.3.2 [func.wrap.func.con] p8+p21 as indicated:
template<class F> function(F f); template <class F, class A> function(allocator_arg_t, const A& a, F f);[…]
-8- Remarks: These constructors shall not participate in overload resolution unlessis
fFLvalue-Callable
(22.10.17.3 [func.wrap.func]) for argument typesArgTypes...
and return typeR
.[…]
template<class F> function& operator=(F&& f);[…]
-21- Remarks: This assignment operator shall not participate in overload resolution unlessis
declval<typename decay<F>::type&>()decay_t<F>Lvalue-Callable
(22.10.17.3 [func.wrap.func]) for argument typesArgTypes...
and return typeR
.
Change 22.10.17.3.6 [func.wrap.func.targ] p2 as indicated:
template<class T> T* target() noexcept; template<class T> const T* target() const noexcept;-2- Requires:
-3- Returns: IfT
shall be a type that isLvalue-Callable
(22.10.17.3 [func.wrap.func]) for parameter typesArgTypes
and return typeR
.target_type() == typeid(T)
a pointer to the stored function target; otherwise a null pointer.