Having a generic parameter type placeholder
signifies that the function is
an abbreviated function template ([dcl.fct]) or
the lambda is a generic lambda ([expr.prim.lambda]).
A placeholder type can appear in
the decl-specifier-seq or type-specifier-seq
in the declared return type of a function declarator that declares a function;
the return type of the function is
deduced from non-discarded return statements, if any, in the body
of the function ([stmt.if]).
[Example 1: auto x =5; // OK, x has type intconstauto*v =&x, u =6; // OK, v has type const int*, u has type const intstaticauto y =0.0; // OK, y has type doubleautoint r; // error: auto is not a storage-class-specifierauto f()->int; // OK, f returns intauto g(){return0.0; }// OK, g returns doubleauto(*fp)()->auto= f; // OKauto h(); // OK, h's return type will be deduced when it is defined — end example]
The type of each declared variable is determined
by placeholder type deduction,
and if the type that replaces the placeholder type is not the
same in each deduction, the program is ill-formed.
[Example 2: auto x =5, *y =&x; // OK, auto is intauto a =5, b ={1, 2}; // error: different types for auto — end example]
If a function with a declared return type that contains a placeholder type has
multiple non-discarded return statements, the return type is deduced for each
such return statement.
If the type deduced is not the same in each
deduction, the program is ill-formed.
If a function with a declared return type that uses a placeholder type has no
non-discarded return statements, the return type is deduced as though from a
return statement with no operand at the closing brace of the function
body.
[Example 3: auto f(){}// OK, return type is voidauto* g(){}// error: cannot deduce auto* from void() — end example]
An exported function
with a declared return type that uses a placeholder type
shall be defined in the translation unit
containing its exported declaration,
outside the private-module-fragment (if any).
If a variable or function with an undeduced placeholder type is named by an
expression ([basic.def.odr]), the program is ill-formed.
Once a
non-discarded return statement has been seen in a function, however, the return type deduced
from that statement can be used in the rest of the function, including in other
return statements.
[Example 4: auto n = n; // error: n's initializer refers to nauto f();
void g(){&f; }// error: f's return type is unknownauto sum(int i){if(i ==1)return i; // sum's return type is intelsereturn sum(i-1)+i; // OK, sum's return type has been deduced} — end example]
Return type deduction for a templated
function with a placeholder in its
declared type occurs when the definition is instantiated even if the function
body contains a return statement with a non-type-dependent operand.
Therefore, any use of a specialization of the function template will
cause an implicit instantiation.
Any errors that arise from this instantiation
are not in the immediate context of the function type and can result in the
program being ill-formed ([temp.deduct]).
— end note]
[Example 5: template<class T>auto f(T t){return t; }// return type deduced at instantiation timetypedefdecltype(f(1)) fint_t; // instantiates f<int> to deduce return typetemplate<class T>auto f(T* t){return*t; }void g(){int(*p)(int*)=&f; }// instantiates both fs to determine return types,// chooses second — end example]
If a function or function template F has
a declared return type that uses a placeholder type,
redeclarations or specializations of F shall use that
placeholder type, not a deduced type;
otherwise, they shall not use a placeholder type.
[Example 6: auto f();
auto f(){return42; }// return type is intauto f(); // OKint f(); // error: auto and int don't matchdecltype(auto) f(); // error: auto and decltype(auto) don't matchtemplate<typename T>auto g(T t){return t; }// #1templateauto g(int); // OK, return type is inttemplatechar g(char); // error: no matching templatetemplate<>auto g(double); // OK, forward declaration with unknown return typetemplate<class T> T g(T t){return t; }// OK, not functionally equivalent to #1templatechar g(char); // OK, now there is a matching templatetemplateauto g(float); // still matches #1void h(){return g(42); }// error: ambiguoustemplate<typename T>struct A {friend T frf(T);
};
auto frf(int i){return i; }// not a friend of A<int>externint v;
auto v =17; // OK, redeclares vstruct S {staticint i;
};
auto S::i =23; // OK — end example]
An explicit instantiation declaration does not cause the
instantiation of an entity declared using a placeholder type, but it also does
not prevent that entity from being instantiated as needed to determine its
type.
[Example 7: template<typename T>auto f(T t){return t; }externtemplateauto f(int); // does not instantiate f<int>int(*p)(int)= f; // instantiates f<int> to determine its return type, but an explicit// instantiation definition is still required somewhere in the program — end example]
For a non-discarded return statement that occurs
in a function declared with a return type
that contains a placeholder type,
T is the declared return type.
For a non-type template parameter declared with a type
that contains a placeholder type,
T is the declared type of the non-type template parameter
and E is the corresponding template argument.
Obtain P from
T by replacing the occurrences of
type-constraintoptauto either with
a new invented type template parameter U or,
if the initialization is copy-list-initialization, with
std::initializer_list<U>.
If the deduction fails, the declaration is ill-formed.
Otherwise, T′ is obtained by
substituting the deduced U into P.
[Example 1: auto x1 ={1, 2}; // decltype(x1) is std::initializer_list<int>auto x2 ={1, 2.0}; // error: cannot deduce element typeauto x3{1, 2}; // error: not a single elementauto x4 ={3}; // decltype(x4) is std::initializer_list<int>auto x5{3}; // decltype(x5) is int — end example]
The type of i is the deduced type of the parameter u in
the call f(expr) of the following invented function template:
template<class U>void f(const U& u);
The type deduced for T is
determined as described in [dcl.type.decltype], as though
E had
been the operand of the decltype.
[Example 3: int i;
int&& f();
auto x2a(i); // decltype(x2a) is intdecltype(auto) x2d(i); // decltype(x2d) is intauto x3a = i; // decltype(x3a) is intdecltype(auto) x3d = i; // decltype(x3d) is intauto x4a =(i); // decltype(x4a) is intdecltype(auto) x4d =(i); // decltype(x4d) is int&auto x5a = f(); // decltype(x5a) is intdecltype(auto) x5d = f(); // decltype(x5d) is int&&auto x6a ={1, 2}; // decltype(x6a) is std::initializer_list<int>decltype(auto) x6d ={1, 2}; // error: { 1, 2 } is not an expressionauto*x7a =&i; // decltype(x7a) is int*decltype(auto)*x7d =&i; // error: declared type is not plain decltype(auto)auto f1(int x)->decltype((x)){return(x); }// return type is int&auto f2(int x)->decltype(auto){return(x); }// return type is int&& — end example]