Can std::is_invocable be emulated within C++11?
Asked Answered
C

2

20

I'd like to use std::is_invocable, but it is available only since C++17, and we are using C++11 Standard.

Is there any way to emulate the functionality using C++11?

Convex answered 5/7, 2018 at 9:36 Comment(5)
There seems to be an equivalent in the gcc implementation.Valorize
Did you try the std::__is_invocable?Valorize
did you check Boost library, boost.org/doc/libs/master/libs/hof/doc/html/include/boost/hof/…Aboutface
@Valorize -- std:__is_invocable is not part of C++11, nor of any other version of the C++ standard. It looks like an interanal detail for a particular library implementation.Roxieroxine
@PeteBecker I see. The comment few lines back suggests it is some sort of equivalent for the C++11. Or that was my understanding.Valorize
A
26

You can try this implementation:) Taken from boost C++ libraries. I've tested it with VS2017 with standard C++14.

template <typename F, typename... Args>
struct is_invocable :
    std::is_constructible<
        std::function<void(Args ...)>,
        std::reference_wrapper<typename std::remove_reference<F>::type>
    >
{
};

template <typename R, typename F, typename... Args>
struct is_invocable_r :
    std::is_constructible<
        std::function<R(Args ...)>,
        std::reference_wrapper<typename std::remove_reference<F>::type>
    >
{
};
Abyssinia answered 5/7, 2018 at 9:54 Comment(1)
It requires the library to implement LWG 2132 though.Ucayali
G
2

The is_invocable type trait is true if the type T can be invoked with the variadic number of arguments Args.

To implement the condition, it is sufficient to verify whether the expression std::bind(std::declval<T>(), std::declval<Args>()...) is well-formed.

Example:

namespace detail {
  template <typename, typename, typename = void>
  struct is_invocable
   : std::false_type {};

  template <typename T, typename ...Args>
  struct is_invocable<T, std::tuple<Args...>, std::void_t<decltype(std::bind(std::declval<T>(), std::declval<Args>()...))>>
   : std::true_type {};
}

template <typename T, typename ...Args>
struct is_invocable
 : detail::is_invocable<T, std::tuple<Args...>> {};

It is important to note that, to realize the type trait, it is necessary to implement a little trick. Indeed, the implementation requires to default the last template parameter to void, but it does not allow to insert a variadic number of template parameters. The adopted solution exploits a helper structure that must be specialized with the variadic pack and then passed as a single template argument. In this case, std::tuple has been used for simplicity. The partial specialization of the type trait tries to deduce the template arguments of the specialized helper structure, extracting the pack again.

Even if the std::void_t type trait is available only since C++17, it can be implemented from scratch.

Example:

template <typename...>
using void_t = void;
Gustie answered 2/6 at 12:53 Comment(8)
If I recall correctly, the trait should work for function pointers (including class members), but I'm not sure yours will. I'm on mobile, though so I can't check.Gratia
It seems to work with function pointers too.Gustie
It appears to fail for member function pointers: DemoGratia
@AndyG, the bug has been fixed. Thanks for your feedback.Gustie
Thanks for the fix. I tested it with function pointers, member function pointers (const and non-const), lambdas, and function objects. While I think the current solution is good and merits an upvote, it still lacks parity with std::is_invocable in the case of function objects Demo. A final nitpick is that I'd probably prefer an alias instead of inheritance in the actual trait. E.g., using is_invocable = detail::is_invocable<T, std::tuple<Args...>>Gratia
@AndyG, thanks again for the help. I think that the problem about function objects can not be solved with std::bind, but the OP explicitly requires C++11. About the inheritance - aliasing, I have chosen the first one for consistency with other type traits (consistency across implementation of type traits in my posts).Gustie
Indeed, you'd want to specialize for types that have operator() defined.Gratia
@AndyG, you are right. However, it requires to stretch the original type trait for an almost irrelevant bug.Gustie

© 2022 - 2024 — McMap. All rights reserved.