1

I'm trying to do the following:

struct Unwrapper
{
    template<typename T>
    auto operator()(const T& arg, std::enable_if_t<isPrimitive<T>, void>* = nullptr) {return arg;}

    template<typename T>
    auto operator()(const T& arg, std::enable_if_t<!isPrimitive<T>, void>* = nullptr) {return arg.wrapped();}

    void operator()(void) {}
};

template<typename T>
using UnwrappedT = std::invoke_result_t<Unwrapper, T>; // error: no type named ‘type’ in ‘struct std::invoke_result<Unwrapper, void>’

The docs for std::invoke_result suggests it should work for Args being void (i.e. none), specifically it says the void case not working was a "quirk" of the now deprecated std::result_of.

But no, void doesn't work. It kind of makes sense because one also can't do std::declval<T>() for T = void, and std::invoke_result is supposed to be implemented in terms of std::declval.

Question is, what's the most elegant/direct way to patch the code to work with void? I could do something with std::conditional but I expected better. (using C++17)

Relevant question: this, this.

haelix
  • 4,245
  • 4
  • 34
  • 56
  • Variadic template ? – Jarod42 Jan 07 '19 at 11:16
  • good shout, but doesn't work. It is not a problem to call `unwrap()` with no-args, it's just `invoke_result_t` not working – haelix Jan 07 '19 at 11:44
  • 1
    What do you mean by "work with void"? An `Unwrapper` can't be called with an object of type `void` (because there's no objects of type `void`), so `invoke_result` is arguably doing what it's expected to do here. – cpplearner Jan 07 '19 at 11:45
  • @cpplearner `void` is a type, not to a full extent (as in creating and passing objects _of it_), but yes to some extents, for example you can use `return void()` syntax for uniformity with templates. I believe `void` is also a type to the extent to which you can ask "what's the return type of a method returning void". The answer is "it is `void`". – haelix Jan 07 '19 at 11:51
  • 2
    Well if you use `std::invoke_result_t` you will get `void`, but `std::invoke_result_t` does not exist, because `void` can't be an argument. – cpplearner Jan 07 '19 at 11:58

2 Answers2

1

You could do this:

template<typename... T>
using UnwrappedT = std::invoke_result_t<Unwrapper, T...>; 

UnwrappedT<> would handle the void case.

If you want UnwrappedT<void> to mean UnwrappedT<>, you'll need some way of dropping the void. conditional is the most familiar way of doing thay:

template<typename T>
using UnwrappedT = typename std::conditional_t<
    std::is_void_v<T>,
    std::invoke_result<Unwrapper>,
    std::invoke_result<Unwrapper, T>>::type;

Or you could have some fun with Boost.Mp11:

template<typename T>
using UnwrappedT = mp_apply<std::invoke_result_t,
    mp_remove_if<mp_list<Unwrapper, T>, std::is_void>>;
Barry
  • 286,269
  • 29
  • 621
  • 977
0

Have you tried ?

template<typename T>
using UnwrappedT = std::result_of_t<Unwrapper(const T&)>;
user982042
  • 329
  • 3
  • 7