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 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
|
In section ref(AUTO) the tt(auto) keyword was introduced. The keyword
tt(decltype), related to tt(auto), shows somewhat different behavior. This
section concentrates on tt(decltype). Different from tt(auto), which requires
no further specifications, tt(decltype) is always followed by an
expression between parentheses (e.g., tt(decltype(variable))).
As an initial illustration, assume we have a function defining a parameter
tt(std::string const &text). Inside the function we may encounter the
following two definitions:
verb( auto scratch1{text};
decltype(text) scratch2 = text;)
With tt(auto) the compiler deduces a plain type, so tt(scratch1) is a
tt(string), and copy construction is used to initialize it from
`tt(text)'.
Now consider tt(decltype): tt(decltype) determines tt(text's) type:
tt(string const &), which is thereupon used as tt(scratch2's) type: tt(string
const &scratch2), referring to whatever string tt(text) refers to. This is
tt(decltype's) standard behavior: when provided with a variable's name,
it is replaced by that variable's type.
Alternatively, an expression can be specified when using tt(decltype). Of
course, a variable is an expression by itself, but in the context of
tt(decltype) we define an `expression' as any expression that is more complex
than just a plain variable specification. But it may be as simple as
tt((variable)): the name of a variable between parentheses.
When an expression is used, the compiler determines whether a reference could
be appended to the expression's type. If so, tt(decltype(expression)) is
replaced by the type of such an lvalue reference (so you get
tt(expression-type &)). If not, tt(decltype(expression)) is replaced by the
expression's plain type.
Here are some examples:
verb( int *ptr;
decltype(ptr) ref = ptr;
// decltype's argument is a plain variable, and so
// ptr's type is used: int *ref = ptr.
// decltype(ptr) is replaced by int *.
// (resulting in two warnings about not-initialized/used variables).
int *ptr;
decltype( (ptr) ) ref = ptr;
// decltype's argument is an expression, and so
// int *&ref = ptr is used.
// decltype( (ptr) ) is replaced by int *&.
int value;
decltype(value + value) var = value + value;
// decltype's argument is an expression, and so the compiler tries
// to replace decltype(...) by int & (int &var = value + value)
// since value + value is a temporary, var's type cannot be int &
// and so decltype(...) is replaced by int
// (i.e., value + value's type)
string lines[20];
decltype(lines[0]) ref = lines[6];
// decltype's argument is an expression, so
// string &ref = lines[6] is used.
// decltype(...) is replaced by string &
string &&strRef = string{};
decltype(strRef) ref = std::move(strRef);
// decltype's argument is a plain variable so the variable's
// type is used: string &&ref = std::move(strRef).
// decltype(...) is replaced by string &&
string &&strRef2 = string{}
decltype((strRef2)) ref2 = strRef2;
// decltype's argument is an expression, so
// string && &ref = strRef is used. This automatically becomes
// string &ref = strRef which is OK
// decltype is replaced by string &.)
In addition to this, ti(decltype(auto)) specifications can be used, in
which case tt(decltype's) rules are applied to tt(auto). So, tt(auto) is used
to determine the type of the initializing expression. Then, if the
initializing expression is a mere variable, then the expression's type is
used. Otherwise, if a reference can be added to the expression's type then
tt(decltype(auto)) is replaced by a reference to the expression's type. Here
are some examples:
verb( int *ptr;
decltype(auto) ptr2 = ptr;
// auto produces ptr's type: int *, ptr is a plain variable, so
// decltype(auto) is replaced by int *
int value;
decltype(auto) ret = value + value;
// auto produces int, value + value is an expression, so int & is
// attempted. However, value + value cannot be assigned to a
// reference so the expression's type is used:
// decltype(auto) is replaced by int
string lines[20];
decltype(auto) line = lines[0];
// auto produces string, lines[0] is an expression, so string & is
// attempted. string &line = lines[0] is OK, so
// decltype(auto) is replaced by string &
decltype(auto) ref = string{}
// auto produces string, string{} is an expression, so string & is
// attempted. However, string &ref = string{} is not a valid
// initialization, so string itself is used:
// decltype(auto) is replaced by string)
In practice, the tt(decltype(auto)) form is most often encountered with
function templates to define return types. Have a look at the following
struct definition (not using function templates, but illustrating the workings
of tt(decltype(auto))):
verb( struct Data
{
vector<string> d_vs;
string *d_val = new string[10];
Data()
:
d_vs(1)
{}
auto autoFun() const
{
return d_val[0];
}
decltype(auto) declArr() const
{
return d_val[0];
}
decltype(auto) declVect() const
{
return d_vs[0];
}
};)
itemization(
it() The member tt(autoFun) returns tt(auto). Since tt(d_val[0]) is passed to
tt(auto), tt(auto) is deducing as tt(string), and the function's return
type is tt(string);
it() The member tt(declArr) returns tt(decltype(auto)). Since tt(d_val[0])
is an expression, representing a tt(string), tt(decltype(auto)) is deduced
as tt(string &), which becomes the function's return type.
it() The member tt(declVect) returns tt(decltype(auto)). Since tt(d_vs[0])
is an expression, representing tt(string), tt(decltype(auto)) is deduced
as tt(string &). However, since tt(declVect) is also
a const member, this reference should be a tt(string const &). This is
recognized by tt(decltype(auto)), and so the function's return type
becomes tt(string const &).
)
If you're wondering why there's no tt(const) in tt(declArr's) return type
while there is one in tt(declVect's) return type then have a look at tt(d_vs)
and tt(d_val): both are constant in the context of their functions, but
tt(d_val), so a tt(const *), points to non-const tt(string) objects. So,
tt(declArr) does em(not) have to return a tt(string const &), whereas
tt(declVect) em(should) return a tt(string const &).
|