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]).
Otherwise, the function declarator
shall declare a function.
If the declared return type of the
function contains a placeholder type, 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 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]