File: autodecl.yo

package info (click to toggle)
c%2B%2B-annotations 13.02.02-1
  • links: PTS, VCS
  • area: main
  • in suites: forky
  • size: 13,576 kB
  • sloc: cpp: 25,297; makefile: 1,523; ansic: 165; sh: 126; perl: 90; fortran: 27
file content (159 lines) | stat: -rw-r--r-- 6,768 bytes parent folder | download | duplicates (4)
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 &).