I asked a question that has several references to the code:
template <typename...>
using void_t = void;
I believe I have a generally misunderstand alias templates:
Why wouldn't you just evaluate whatever template parameter you're passing into an alias template in an enable_if_t
or conditional_t
statement?
Is the code above just about doing an enable_if_t
on multiple template parameters at once?
Secondly, I believe that I have a specific misunderstanding of the role of void_t
. This comment states that the C++17 standard defines void_t
. Here's what I don't get:
Isn't void_t
just an arbitrary name? If I still have to define template <typename...> using void_t = void;
wherever I plan to use void_t
what's the point of standardizing an arbitrary name?
I don't think the shown example really shows what void_t
is good for as it only shows one use case, but when you look at
template<typename T>
struct has_to_string<T,
void_t<decltype(std::to_string(std::declval<T>()))>
>
: std::true_type { };
it is not so much different from
template<typename T>
struct has_to_string<T,
decltype(std::to_string(std::declval<T>()), void())
>
: std::true_type { };
And for this statement:
The former version is much easier to read and
void_t
doesn't requiredecltype
to work.
I think the advantage in readability is quite small and the second part makes no sense, when decltype
doesn't work, SFINAE kicks in as expected.
One example where void_t
is more useful is the one from the proposal:
// primary template handles types that have no nested ::type member
template< class, class = void_t<> >
struct has_type_member
: std::false_type { };
// specialization recognizes types that do have a nested ::type member
template< class T >
struct has_type_member<T, void_t<typename T::type>>
: std::true_type { }
As you can see, even the primary template uses void_t
to increase the readability as it now matches the specialization. That is not strictly necessary, but I like it. The real power comes when you think about the alternatives. Without void_t
, the specialization is now more complicated:
template< class T >
struct has_type_member<T, decltype(typename T::type, void())>
: std::true_type { }
wouldn't work as T::type
names a type, not an expression. You therefore need
template< class T >
struct has_type_member<T, decltype(std::declval<typename T::type>(), void())>
: std::true_type { }
The whole expression becomes longer, more tricky and it might suffer from edge-cases you forgot to handle. This is where void_t
really helps, the other uses are then just a small improvement and they increase consistency.