It gets worse. If you want to use the typedefinside a template for the purpose of creating a linked list holding objects of a type specified by a template parameter, you have to precede the typedefname with typename:
template
class Widget { // Widget contains
private: // a MyAllocList
typenameMyAllocList::type list; // as a data member
…
};
Here, MyAllocList::typerefers to a type that's dependent on a template type parameter ( T). MyAllocList::typeis thus a dependent type , and one of C++'s many endearing rules is that the names of dependent types must be preceded by typename.
If MyAllocListis defined as an alias template, this need for typenamevanishes (as does the cumbersome “ ::type” suffix):
template
using MyAllocList = std::list>; // as before
template
class Widget {
private:
MyAllocListlist; // no "typename",
… // no "::type"
};
To you, MyAllocList(i.e., use of the alias template) may look just as dependent on the template parameter Tas MyAllocList::type(i.e., use of the nested typedef), but you're not a compiler. When compilers process the Widget template and encounter the use of MyAllocList(i.e., use of the alias template), they know that MyAllocListis the name of a type, because MyAllocListis an alias template: it must name a type. MyAllocListis thus a non-dependent type , and a typenamespecifier is neither required nor permitted.
When compilers see MyAllocList::type(i.e., use of the nested typedef) in the Widgettemplate, on the other hand, they can't know for sure that it names a type, because there might be a specialization of MyAllocListthat they haven't yet seen where MyAllocList::typerefers to something other than a type. That sounds crazy, but don't blame compilers for this possibility. It's the humans who have been known to produce such code.
For example, some misguided soul may have concocted something like this:
class Wine { … };
template<> // MyAllocList specialization
class MyAllocList { // for when T is Wine
private:
enum class WineType // see Item 10 for info on
{ White, Red, Rose }; // "enum class"
WineType type; // in this class, type is
… // a data member !
};
As you can see, MyAllocList::typedoesn't refer to a type. If Widgetwere to be instantiated with Wine, MyAllocList::typeinside the Widgettemplate would refer to a data member, not a type. Inside the Widgettemplate, then, whether MyAllocList::typerefers to a type is honestly dependent on what T is, and that's why compilers insist on your asserting that it is a type by preceding it with typename.
If you've done any template metaprogramming (TMP), you've almost certainly bumped up against the need to take template type parameters and create revised types from them. For example, given some type T, you might want to strip off any const-or reference-qualifiers that Tcontains, e.g., you might want to turn const std::string&into std::string. Or you might want to add constto a type or turn it into an lvalue reference, e.g., turn Widgetinto const Widget or into Widget&. (If you haven't done any TMP, that's too bad, because if you want to be a truly effective C++ programmer, you need to be familiar with at least the basics of this facet of C++. You can see examples of TMP in action, including the kinds of type transformations I just mentioned, in Items 23and 27.)
C++11 gives you the tools to perform these kinds of transformations in the form of type traits , an assortment of templates inside the header . There are dozens of type traits in that header, and not all of them perform type transformations, but the ones that do offer a predictable interface. Given a type T to which you'd like to apply a transformation, the resulting type is std:: transformation ::type. For example:
std:: remove_const::type // yields T from const T
std:: remove_reference::type // yields T from T& and T&&
std:: add_lvalue_reference::type // yields T& from T
The comments merely summarize what these transformations do, so don't take them too literally. Before using them on a project, you'd look up the precise specifications, I know.
My motivation here isn't to give you a tutorial on type traits, anyway. Rather, note that application of these transformations entails writing “ ::type” at the end of each use. If you apply them to a type parameter inside a template (which is virtually always how you employ them in real code), you'd also have to precede each use with typename. The reason for both of these syntactic speed bumps is that the C++11 type traits are implemented as nested typedefs inside templatized structs. That's right, they're implemented using the type synonym technology I've been trying to convince you is inferior to alias templates!
There's a historical reason for that, but we'll skip over it (it's dull, I promise), because the Standardization Committee belatedly recognized that alias templates are the better way to go, and they included such templates in C++14 for all the C++11 type transformations. The aliases have a common form: for each C++11 transformation std:: transformation ::type, there's a corresponding C++14 alias template named std:: transformation _t. Examples will clarify what I mean:
std::remove_const::type // C++11: const T → T
std::remove_const _t // C++14 equivalent
std::remove_reference::type // C++11: T&/T&& → T
std::remove_reference _t // C++14 equivalent
std::add_lvalue_reference::type // C++11: T → T&
std::add_lvalue_reference _t // C++14 equivalent
The C++11 constructs remain valid in C++14, but I don't know why you'd want to use them. Even if you don't have access to C++14, writing the alias templates yourself is child's play. Only C++11 language features are required, and even children can mimic a pattern, right? If you happen to have access to an electronic copy of the C++14 Standard, it's easier still, because all that's required is some copying and pasting. Here, I'll get you started:
template
using remove_const_t = typename remove_const::type;
template
using remove_reference_t = typename remove_reference::type;
template
using add_lvalue_reference_t =
typename add_lvalue_reference::type;
See? Couldn't be easier.
Things to Remember
• typedefs don't support templatization, but alias declarations do.
• Alias templates avoid the “ ::type” suffix and, in templates, the “ typename” prefix often required to refer to typedefs.
Читать дальше