1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
|
Here are some examples of how simple definitions where the compiler deduces
template arguments can (and cannot) be specified.
Starting point:
verb( template <class ...Types> // any set of types
class Deduce
{
public:
Deduce(Types ...params); // constructors
void fun(); // a member function
};)
Some definitions:
verb( // deduce: full definition:
// --------------------------------
Deduce first{1}; // 1: int -> Deduce<int> first{1}
Deduce second; // no Types -> Deduce<> second;
Deduce &&ref = Deduce<int>{ 1 };// int -> Deduce<int> &&ref
template <class Type>
Deduce third{ static_cast<Type *>(0) };)
The template tt(third) is a recipe for constructing tt(Deduce)
objects from a type that's specified for tt(third). The pointer's type simply
is a pointer to the specified type (so, specifying tt(third<int>) implies an
tt(int *)). Now that the type of tt(third)'s argument is available (i.e., an
tt(int *)) the compiler deduces that tt(third{0}) is a tt(Deduce<int *>).
This tt(Deduce<int *>) object could, e.g., be used to initialize a named
tt(Deduce<int *>) object:
verb( auto x = third<int>; // x's full definition: Deduce<int *> x{0})
tt(Deduce's) member functions can be used by anonymous and named objects:
verb( x.fun(); // OK: fun called by named object
third<int>.fun(); // OK: fun called by anonymous object)
Here are some examples that won't compile:
verb( extern Deduce object; // not an object definition
Deduce *pointer = 0; // any set of types could be intended
Deduce function(); // same.)
When defining objects, either using function-type or curly brace definitions
template argument deduction is realized as follows:
itemization(
it() First, a list of available constructors is formed. The list contains
all available ordinary constructors and constructor templates (i.e,
constructors defined as member templates);
it() For each element of that list a parallel imaginary function
is formed by the compiler (forming function templates for constructor
templates, and ordinary functions for ordinary constructors);
it() The return types of these imaginary functions are the class types of
the constructors, using the template parameters of the original class
template. For example, for the tt(Deduce::Deduce) constructor the imaginary
function
verb( template <class ...Types>
Deduce<Types ...> imaginary(Types ...params);)
is formed.
it() Next, ordinary argument deduction and overload resolution is applied
to the set of imaginary functions. If this results in a best match for
one of these imaginary functions then that function's class type (or
specialization) will be used. Otherwise the program is ill-formed.
)
Let's apply this process to the class tt(Deduce). The set of imaginary
functions matching tt(Deduce) looks like this:
verb( // already encountered: matching
template <class ...Types> // Deduce(Types ...params)
Deduce<Types ...> imaginary(Types ...params);
// the copy constructor: matching
template <class ...Types> // Deduce(Deduce<Types ...> const &)
Deduce<Types ...> imaginary(Deduce<Types ...> const &other);
// the move constructor, matching
template <class ...Types> // Deduce(Deduce<Types ...> &&)
Deduce<Types ...> imaginary(Deduce<Types ...> &&tmp);)
For the construction tt(Deduce first{1}) the first imaginary function wins
the overload contest, resulting in the template argument deduction tt(int) for
tt(class ...Types), and hence tt(Deduce<int> first{1}) is defined.
Note that when a class template is nested under a class template, the nested
class template's name depends on the outer class type. The outer class then
provides the name qualifier for the inner class template. In such cases
template argument deduction is used for the nested class, but (as it is not
used for name qualifiers) is not used for the outer class. Here is an example:
adding a nested class template to tt(Outer):
verb( template <class OuterType>
class Outer
{
public:
template <class InnerType>
struct Inner
{
Inner(OuterType);
Inner(OuterType, InnerType);
template <typename ExtraType>
Inner(ExtraType, InnerType);
};
};
// defining:
Outer<int>::Inner inner{2.0, 1};)
In this case, the compiler uses these imaginary functions:
verb( template <typename InnerType>
Outer<int>::Inner<InnerType> // copy constructor
imaginary(Outer<int>::Inner<InnerType> const &);
template <typename InnerType>
Outer<int>::Inner<InnerType> // move constructor
imaginary(Outer<int>::Inner<InnerType> &&);
template <typename InnerType> // first declared constructor
Outer<int>::Inner<InnerType> imaginary(int);
template <typename InnerType> // second declared constructor
Outer<int>::Inner<InnerType> imaginary(int, InnerType);
template <typename InnerType> // third declared constructor
template <typename ExtraType>
Outer<int>::Inner<InnerType> imaginary(ExtraType, InnerType);)
Template argument deduction for calling tt(imaginary(2.0, 1)) results in
tt(double) for its first argument and tt(int) for its second. Overload
resolution then favors the last imaginary function, and so tt(ExtraType:
double) and tt(InnerType: int). Consequently,
verb( Outer<int>::Inner inner{ 2.0, 1 };)
is defined as:
verb( Outer<int>::Inner<int> Inner{ 2.0, 1 };)
|