std::function
's member template target()
should not lead to undefined behaviourSection: 22.10.17.3.6 [func.wrap.func.targ] Status: C++17 Submitter: Daniel Krügler Opened: 2016-01-31 Last modified: 2017-09-07
Priority: 3
View all other issues in [func.wrap.func.targ].
View all issues with C++17 status.
Discussion:
This issue is a spin-off of LWG 2393, it solely focuses on the pre-condition of 22.10.17.3.6 [func.wrap.func.targ] p2:
Requires:
T
shall be a type that is Callable (20.9.12.2) for parameter typesArgTypes
and return typeR
.
Originally, the author of this issue here had assumed that simply removing the precondition as a side-step of fixing LWG 2393 would be uncontroversial. Discussions on the library reflector indicated that this is not the case, although it seemed that there was agreement on removing the undefined behaviour edge-case.
There exist basically the following positions:The constraint should be removed completely, the function is considered as having a wide contract.
The pre-condition should be replaced by a Remarks element, that has the effect of making the code ill-formed,
if T
is a type that is not Lvalue-Callable (20.9.11.2) for parameter types ArgTypes
and return type R
.
Technically this approach is still conforming with a wide contract function, because the definition of this contract form
depends on runtime constraints.
Not yet explicitly discussed, but a possible variant of bullet (2) could be:
The pre-condition should be replaced by a Remarks element, that has the effect of SFINAE-constraining
this member: "This function shall not participate in overload resolution unless T
is a type that is
Lvalue-Callable (20.9.11.2) for parameter types ArgTypes
and return type R
".
The following describes a list of some selected arguments that have been provided for one or the other position using corresponding list items. Unless explicitly denoted, no difference has been accounted for option (3) over option (2).
It reflects existing implementation practice, Visual Studio 2015 SR1, gcc 6 libstdc++, and clang 3.8.0 libc++ do accept the following code:
#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<void(*)()>() << std::endl; boost::function<void(int)> f2(foo); std::cout << f2.target<void(*)()>() << std::endl; }
and consistently output the implementation-specific result for two null pointer values.
The current
Boost documentation
does not indicate any precondition for calling the target
function, so it is natural
that programmers would expect similar specification and behaviour for the corresponding standard component.
There is a consistency argument in regard to the free function template get_deleter
template<class D, class T> D* get_deleter(const shared_ptr<T>& p) noexcept;
This function also does not impose any pre-conditions on its template argument D
.
Programmers have control over the type they're passing to target<T>()
. Passing a non-callable type
can't possibly retrieve a non-null target, so it seems highly likely to be programmer error. Diagnosing that at
compile time seems highly preferable to allowing this to return null, always, at runtime.
If T
is a reference type then the return type T*
is ill-formed anyway. This implies that one can't
blindly call target<T>
without knowing what T
is.
It has been pointed out that some real world code, boiling down to
void foo() {} int main() { std::function<void()> f = foo; if (f.target<decltype(foo)>()) { // fast path } else { // slow path } }
had manifested as a performance issue and preparing a patch that made the library static_assert
in that
case solved this problem (Note that decltype(foo)
evaluates to void()
, but a proper argument of
target()
would have been the function pointer type void(*)()
, because a function type
void()
is not any Callable type).
It might be worth adding that if use case (2 c) is indeed an often occurring idiom, it would make sense to consider
to provide an explicit conversion to a function pointer (w/o template parameters that could be provided incorrectly),
if the std::function
object at runtime conditions contains a pointer to a real function, e.g.
R(*)(ArgTypes...) target_func_ptr() const noexcept;
[2016-08 Chicago]
Tues PM: Moved to Tentatively Ready
Proposed resolution:
This wording is relative to N4567.
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;-3- Returns: If
-2- Requires:T
shall be a type that isCallable
(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.