简体   繁体   中英

Partial template specialization type collapsing rules

Sorry for the lack of a better title.

While trying to implement my own version of std::move and understanding how easy it was, I'm still confused by how C++ treats partial template specializations. I know how they work, but there's a sort of rule that I found weird and I would like to know the reasoning behind it.

template <typename T>
struct BaseType {
    using Type = T;
};

template <typename T>
struct BaseType<T *> {
    using Type = T;
};

template <typename T>
struct BaseType<T &> {
    using Type = T;
};

using int_ptr = int *;
using int_ref = int &;

// A and B are now both of type int
BaseType<int_ptr>::Type A = 5;
BaseType<int_ref>::Type B = 5;

If there wasn't no partial specializations of RemoveReference , T would always be T : if I gave a int & it would still be a int & throughout the whole template.

However, the partial specialized templates seem to collapse references and pointers: if I gave a int & or a int * and if those types match with the ones from the specialized template, T would just be int .

This feature is extremely awesome and useful, however I'm curious and I would like to know the official reasoning / rules behind this not so obvious quirk.

If your template pattern matches T& to int& , then T& is int& , which implies T is int .

The type T in the specialization only related to the T in the primary template by the fact it was used to pattern match the first argument.

It may confuse you less to replace T with X or U in the specializations. Reusing variable names can be confusing.

template <typename T>
struct RemoveReference {
  using Type = T;
};

template <typename X>
struct RemoveReference<X &> {
  using Type = X;
};

and X& matches T . If X& is T , and T ia int& , then X is int .


Why does the standard say this?

Suppose we look af a different template specialization:

template<class T>
struct Bob;

template<class E, class A>
struct Bob<std::vector<E,A>>{
  // what should E and A be here?
};

Partial specializations act a lot like function templates : so much so, in fact, that overloading function templates is often mistaken for partial specialization of them (which is not allowed). Given

template<class T>
void value_assign(T *t) { *t=T(); }

then obviously T must be the version of the argument type without the (outermost) pointer status, because we need that type to compute the value to assign through the pointer. We of course don't typically write value_assign<int>(&i); to call a function of this type, because the arguments can be deduced.

In this case:

template<class T,class U>
void accept_pair(std::pair<T,U>);

note that the number of template parameters is greater than the number of types "supplied" as input (that is, than the number of parameter types used for deduction): complicated types can provide "more than one type's worth" of information.

All of this looks very different from class templates , where the types must be given explicitly (only sometimes true as of C++17 ) and they are used verbatim in the template (as you said).

But consider the partial specializations again:

template<class>
struct A;                               // undefined
template<class T>
struct A<T*> { /* ... */ };             // #1
template<class T,class U>
struct A<std::pair<T,U>> { /* ... */ }; // #2

These are completely isomorphic to the (unrelated) function templates value_assign and accept_pair respectively. We do have to write, for example, A<int*> to use #1; but this is simply analogous to calling value_assign(&i) : in particular, the template arguments are still deduced , only this time from the explicitly-specified type int* rather than from the type of the expression &i . (Because even supplying explicit template arguments requires deduction, a partial specialization must support deducing its template arguments.)

#2 again illustrates the idea that the number of types is not conserved in this process: this should help break the false impression that " the template parameter" should continue to refer to " the type supplied". As such, partial specializations do not merely claim a (generally unbounded) set of template arguments: they interpret them.

Yet another similarity: the choice among multiple partial specializations of the same class template is exactly the same as that for discarding less-specific function templates when they are overloaded. (However, since overload resolution does not occur in the partial specialization case, this process must get rid of all but one candidate there.)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM