Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What are the use cases of not declaring the name for a typename in template?

Sometimes I see below kind of declaration:

template<typename>  // <-- not "typename T"
struct A { ... };

What are the use-cases for such declaration. Are those useful or just a matter of style ?

like image 853
iammilind Avatar asked Jul 03 '11 05:07

iammilind


1 Answers

Did you really see that use for a template definition, and not for, say, a template declaration (only)?

Some uses:

// declaration only: the parameter name has no use beyond documentation
template<typename>
struct A;

// this is fine
template<typename T>
void eat_an_a(A<T> a);

// later, we can name the parameter to use it
template<typename T>
struct A { ... };

// C++0x only
template<
    typename T
    // We don't care for the actual type (which will default to void)
    // the goal is sfinae
    , typename = typename std::enable_if<
        std::is_array<typename std::decay<T>::type>::value
    >::value
>
void
f(T&& t);

// We still don't care to name that defaulted parameter
template<typename T, typename>
void f(T&& t)
{ ... }

The explanation to the very particular case you linked to has been given by Johannes, but apparently you have found it unsatisfactory. I'm going to walk you through how this works. Let us assume an arbitrary traits class:

// no definition
template<typename TypeToExamine, typename ImplementationDetail = void>
struct trait;

I'm spelling out the role of the type parameters in their names. Now what this declaration allows, since the second parameter is defaulted, is a bit of syntactic sugar. Wherever trait<U> appears, it's exactly as if we've written trait<U, void> had been written. Let us now provide a definition for the base case of our trait:

// assume previous declaration is still in scope so we do not default
// the second parameter again
template<typename T, typename> struct trait: std::false_type {};

It's not a very useful trait. Now when we write trait<U>, which is short for trait<U, void>, we end up with this definition. This means that trait<U>::value is valid and is in fact false. Let's make our class more useful by adding the secret ingredient:

template<typename> struct void_ { typedef void type; };
// again, assume previous declarations are in scope
template<typename T, typename void_<decltype( T() + T() )>::type>
struct trait: std::true_type {};

Again, when we write trait<U>, it's as if we'd written trait<U, void>. The partial specialization doesn't change that (it's not allowed to). But what definition should we use when we query trait<U>::value? Well, first, we have to know what exactly the specialization shoud match; or, what is the mysterious second argument typename void_<decltype( T() + T() )>::type?

The simplest case is when U() + U() is ill-formed. Then SFINAE kicks in and it's as if the specialization didn't exist; thus we get the non-specialized definition, and value is false. If however U() + U() is well-formed, then decltype yields a type, and the whole turns into void, since for all types void_<T>::type is void. So this means that we have a specialization of the form trait<T, void>. This can match trait<U>, simply matching T with U. And now value is true.

If however the specialization would have been written

template<typename T>
struct trait<T, decltype( T() + T() )>: std::true_type {};

then the only way it would be used is when writing trait<U, decltype(U() + U())>, unless decltype(U() + U()) happended to be void. Remember, trait<U> is sugar for trait<U, void>. So trait<int> would never match our specialization because the latter is of the form trait<int, int>.

Thus void_ plays the role to always have specializations of the form trait<T, void> if they are not SFINAE'd out. Since we simply don't care to use the type parameter, it's not named.

like image 128
Luc Danton Avatar answered Sep 29 '22 19:09

Luc Danton