This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of C++17 status.

### 2591. `std::function`'s member template `target()` should not lead to undefined behaviour

Section: 20.14.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 20.14.17.3.6 [func.wrap.func.targ] p2:

> Requires: `T` shall be a type that is Callable (20.9.12.2) for parameter types `ArgTypes` and return type `R`.

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:

1. The constraint should be removed completely, the function is considered as having a wide contract.

2. 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:

3. 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).

1. 1. 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.

   2. 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.

   3. 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`.

2. 1. 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.

   2. 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.

   3. 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.

1. Change 20.14.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: `T` shall be a type that is `Callable` (20.14.17.3 [func.wrap.func]) for parameter types `ArgTypes` and return type `R`.

   > > -3- Returns: If `target_type() == typeid(T)` a pointer to the stored function target; otherwise a null pointer.