Document number:   N4169
Date:   2014-08-22
Project:   Programming Language C++, Library Evolution Working Group
Reply-to:  
Tomasz Kamiński <tomaszkam at gmail dot com>

A proposal to add invoke function template (Revision 1)

Introduction

The aim of this proposal is to introduce the function template invoke that provide uniform semantics for invoking all C++ callable types which includes: function pointers, member pointers and functors. The behaviour of the function is defined in terms of INVOKE expression.

Revision history

Changes since N3727:

Motivation and Scope

The functional components defined introduced in the C++11 has native support for member pointers, that means that these types can be directly used with std::function or std::bind, without need to use wrappers like std::mem_fun, std::mem_fun_ref or std::mem_fn. This uniform support was achieved by introduction of INVOKE expression.

On the other side, there is no standard component, that will provide uniform syntax for calling any callable object with given arguments, so when generic component is developed, the programmer has two options: implement separate handling for member pointers (auto boxing with std::mem_fn) or support only function types. Because of additional implementation burden and relatively small popularity of this feature the second approach is preferred. Most notably the apply function from Library Fundamentals TS lacks of support for member pointers.

Simple call wrapper example

As example, please consider the deref_fn wrapper, that should accept any callable object f and return a functor that invokes provided callable and dereferences the result.

Using existing C++14 standard facilities deref_fn function may be implemented as:

  template<typename F, std::enable_if_t<!std::is_member_pointer<std::decay_t<F>>{}, int> = 0>
  auto deref_fn(F&& f) 
  { 
    return [f](auto&&... args) { return *f(std::forward<decltype(args)>(args)...); };
  } 

  template<typename F, std::enable_if_t<std::is_member_pointer<std::decay_t<F>>{}, int> = 0>
  auto deref_fn(F&& f)
  { 
    return [mf = std::mem_fn(f)](auto&&... args) { return *mf(std::forward<decltype(args)>(args)...); };
  }

Proposed invoke function allows simpler implementation, that does not resort to use of SFINAE function overloading:

  template<typename F>
  auto deref_fn(F&& f) 
  { 
    return [f](auto&&... args) { return *std::invoke(f, std::forward<decltype(args)>(args)...); };
  }

Design Decisions

constexpr implementation

Although there is possibility to implement standard conforming invoke function template as a constexpr function, the proposed wording does not require such implementation. The main reason is to left it consistent with existing standard function objects, that could have such definition, like std::mem_fn, std::reference_wrapper and operator wrappers. Furthermore imposing such requirement will block the implementation of invoke that refers to std::mem_fn.

This proposal assumes that constexpr addition to the <functional> header would be applied consistently by a separate proposal.

Both constexpr and standard library based implementation are presented in Implementability section of the proposal.

Replacing INVOKE expression

The wording of this proposal may also be specified by moving the requirements of the INVOKE expression to definition of the invoke function template and replacing all existing references to the INVOKE expression by corresponding invoke function invocation.

The approach described above is not used in this proposal, primarily to give implementation freedom to the standard library providers (e.g., whether invoke should be defined in terms of mem_fn or vice versa). In addition such approach will require changes in existing implementations of standard function objects, without providing any real benefit from the library user perspective.

Overload with explicit return type

First revision of the paper definied second overload of the proposed function (invoke<R>(args...)), mapped to INVOKE expression with explicit specification of return type (INVOKE(args..., R)). Mentioned version is leftover from TR1 implementation, were result type was determined using result_of protocol or has to be specified at call side and, after the introduction of type interference in C++11, it becomes obsolete.

Impact On The Standard

This proposal has no dependencies beyond a C++11 compiler and Standard Library implementation. (It depends on perfect forwarding, varidatic templates, decltype and trailing return types.)

Nothing depends on this proposal.

Proposed wording

After the declaration of binary_function in the section 20.9 [function.objects]/2 (Header <functional> synopsis), add:

  // 20.9.3, invoke
  template <class F, class... Args> result_of_t<F&&(Args&&...)> invoke(F&& f, Args&&... args);

After paragraph 20.9.2 Requirements [func.require], insert a new paragraph. (Chapter [refwrap] (Class template reference_wrapper) becomes 20.9.?)

20.9.3 Function template invoke [func.invoke]

  template <class F, class... Args>
    result_of_t<F&&(Args&&...)> invoke(F&& f, Args&&... args);
Returns:

INVOKE(std::forward<F>(f), std::forward<Args>(args)...) ([func.require] 20.9.2).

Implementability

Proposed invoke function template may be implemented in terms of existing C++11 standard library components:

  template<typename Functor, typename... Args>
  typename std::enable_if<
    std::is_member_pointer<typename std::decay<Functor>::type>::value,
    typename std::result_of<Functor&&(Args&&...)>::type
  >::type invoke(Functor&& f, Args&&... args)
  { 
    return std::mem_fn(f)(std::forward<Args>(args)...); 
  }
   
  template<typename Functor, typename... Args>
  typename std::enable_if<
    !std::is_member_pointer<typename std::decay<Functor>::type>::value,
    typename std::result_of<Functor&&(Args&&...)>::type
  >::type invoke(Functor&& f, Args&&... args)
  { 
    return std::forward<Functor>(f)(std::forward<Args>(args)...); 
  }

An constexpr implemenatation may be found at: https://github.com/tomaszkam/proposals/blob/master/invoke/invoke_cpp11.hpp.

Acknowledgements

Joe Gottman originally proposed invoke function in discussion group ISO C++ Standard - Future Proposals.

Andrzej Krzemieński offered many useful suggestions and corrections to the proposal.

References

  1. Jeffrey Yasskin, "Working Draft, C++ Extensions for Library Fundamentals" (N3421, http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4082.pdf)
  2. Tomasz Kamiński, Implementation of invoke function (https://github.com/tomaszkam/proposals/blob/master/invoke/invoke_cpp11.hpp)