Musings on N4169, std::invoke function template

Jonathan Wakely jwakely@redhat.com
Sun May 3 23:21:00 GMT 2015


During the past 24 hours I've had a lot of time in airports and have
been implementing std::invoke() for C++17, as proposed by
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4169.html

I've done it three different ways now, but I'm not entirely happy with
any of them yet (they all increase the compiler's memory usage
compared to the existing library code, even though they reduce the
overall line count by removing redundancy).

I don't like the reference implementation in N4169:

  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)...);
  }

The problem with this is that it relies on all the logic for INVOKE
being performed by both std::mem_fn and by std::result_of, and so any
changes to the definition of INVOKE (e.g. LWG 2219) need to be done in
two places.

I think ideally we would have a single __invoke() function that
implements INVOKE exactly, then a SFINAE-friendly std::result_of can
be defined in C++11 as:

  template<typename _Fn, typename... _Args>
    using __invoke_result = decltype(
      std::__invoke(declval<_Fn>(), declval<_Args>()...)
    );

  template<typename...> using __void_t = void;

  template<typename _Signature, typename = void>
    struct __result_of
    { };

  template<typename _Fn, typename... _Args>
    struct __result_of<
      _Fn(_Args...),
      __void_t<__invoke_result<_Fn, _Args...>>
    >
    {
      using type = __invoke_result<_Fn, _Args...>;
    };

  template<typename _Signature>
    struct result_of;

  template<typename _Fn, typename... _Args>
    struct result_of<_Fn, _Args...> : __result_of<_Fn(_Args...)> { };

and std::invoke itself is trivial:

  template<typename _Fn, typename... _Args>
    inline result_of_t<_Fn&&(_Args&&...)>
    invoke(_Fn&& __fn, _Args&&... __args)
    {
      return std::__invoke(std::forward<_Fn>(__fn),
                           std::forward<_Args>(__args)...);
    }

and all the operator() and _M_call ugliness in _Mem_fn gets replaced
by simply:

  template<typename... _Args>
    result_type
    operator()(_Args&&... __args) const
    { return std::__invoke(_M_pmf, std::forward<_Args>(__args)...); }

(Or slightly more complicated if we want to add a noexcept spec to the
function, which might be nice, although it increases compile times.)

This all seems *highly* desirable to me: result_of and mem_fn are much
easier to understand and maintain, and any bug fixes or any changes
like 2219 only need to go in one place, in __invoke.

(We'd also replace the current __invoke() which is only used for
reference_wrapper but is dated, flawed and incomplete for C++11's
INVOKE, as it originated in <tr1/functional>).

As my time in airports is (I hope) about to end, should I continue to
spend time on this, and pick one of my multiple implementations of
__invoke, or are there any good reasons to keep our current (very
nice) implementation of result_of and do the actual INVOKE-ing
separately?


(Argh, I spoke too soon, I'm stuck here for another hour ... let's try
to come up with a better __invoke ...)



More information about the Libstdc++ mailing list