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