This proposal also resolves core DRs 975 (lambda return type deduction from multiple return statements) and 1048 (inconsistency between auto and lambda return type deduction).
and if we allow it in that situation, it should be valid in other situations as well. Allowing it is also the more orthogonal choice; in general, I believe that if combining two features can work, it should work. On the other hand, as Alisdair pointed out to me it could lead to unexpected ordering dependence with SFINAE:struct A { auto f(); // forward declaration }; auto A::f() { return 42; }
But then again, we already have the same situation if f returns an incomplete class type which is defined between h1 and h2.auto f(int); template <class T, int = sizeof(f(T()))> void g(); void h1() { g<int>(); } // no match, g() disqualified by SFINAE auto f(int i) { return i; } void h2() { g<int>(); } // OK
The proposed wording allows non-defining declarations so long as all declarations have the same declared type, without considering the deduced type.
And similarly for templates:auto f(); // return type is unknown auto f() { return 42; } // return type is int auto f(); // redeclaration int f(); // error, declares a different function
template <class T> auto g(T t); // forward declaration template <class T> auto g(T t) { return t; } // return type is deduced at instantiation time template <class T> auto g(T t); // redeclaration
Of course, using such a function in an expression when only a forward declaration has been seen is ill-formed:
An explicit specialization or instantiation of an auto template must also use auto. An explicit specialization or instantiation of a non-auto template must not use auto.auto f(); // return type is unknown int i = f(); // error, return type of f is unknown
template <class T> auto f(T t) { return t; } // #1 template auto f(int); // OK template char f(char); // error, no matching template template<> auto f(double); // OK, forward declaration with unknown return type template <class T> T f(T t) { return t; } // OK, not functionally equivalent to #1 template char f(char); // OK, now there is a matching template template auto f(float); // OK, matches #1
The limitation on return type deduction to function bodies consisting of a single return statement is inconvenient for lambdas (DR 975), but is likely to be even more of an annoyance on normal functions.
Both return statements here return int, we can determine that perfectly well. The proposed wording allows this example, and resolves core issue 975.auto iterate(int len) // error, body isn't "return expr;" { for (int i = 0; i < len; ++i) if (search (i)) return i; return -1; }
but once we have deduced a return type, there is no reason to prohibit recursion.auto h() { return h(); } // error, return type of h is unknown
auto sum(int i) { if (i == 1) return i; // return type deduced to int else return sum(i-1)+i; // ok to call it now }
template <class T> auto f(T t) { return t; } // return type deduced at instantiation time typedef decltype(f(1)) fint_t; // must instantiate f<int> to deduce return type
To allow this, we should use the same auto deduction rules for function and lambda return type that we do for auto variables. This would also resolve core DR 1048, which objects to the difference in handling of cv-qualifiers between auto deduction and lambda return type deduction and was classified as an extension.template <class T> struct A { static T t; }; template <class T> auto& f() { return A::t; } // returns by reference
Such a function must have a return statement, however, since there is no way to get void from auto&.
auto& f() { } // error, no return statement
Unfortunately, there is no way to get the effect of decltype with an auto return type; plain auto never deduces to a reference, and auto&& always deduces to a reference. This is a significant problem, as it means that forwarding functions can't use auto. We could consider using decltype semantics instead of the existing auto semantics, but that would mean giving different deduction semantics to auto depending on whether the declaration is of a variable or a function, and making auto functions different from lambdas.
One possibility for addressing this issue would be to change the deduction rules for auto&& specifically; another would be to write decltype(auto) to get the decltype semantics without having to repeat the expression.
The auto type-specifier designates a placeholder type that will be replaced later, by either deduction from an initializer orChange 5.1.2¶4:signifies that the type of a variable being declared shall be deduced from its initializer or that a function declarator shall includea trailing-return-type.The auto type-specifier
maycan appear with a function declarator either in the decl-specifier-seq or type-specifier-seq or as part of a conversion-function-idwith a trailing-return-type (8.3.5)in any context where such a declarator is valid. If the function declarator includes a trailing-return-type (8.3.5), that specifies the declared return type of the function, so the return type is not considered to use auto. Otherwise, the return type of the function is deduced from return statements in the body of the function, if any.
Otherwise, the type of the variableThe type of a variable declared using auto is deduced from its initializer.The name of the variable being declared shall not appear in the initializer expression.This use of auto is allowed when declaring variables in a block (6.3), in namespace scope (3.3.6), and in a for-init-statement (6.5.3). auto shall appear as one of the decl-specifiers in the decl-specifier-seq and the decl-specifier-seq shall be followed by one or more init-declarators, each of which shall have a non-empty initializer.[ Example:
— end example ]auto x = 5; // OK: x has type int const auto *v = &x, u = 6; // OK: v has type const int*, u has type const int static auto y = 0.0; // OK: y has type double auto int r; // error: auto is not a storage-class-specifier auto f() -> int; // OK: f returns int auto g() { return 0.0; } // OK: g returns double auto h(); // OK, h's return type will be deduced when it is definedThe auto type-specifier can also be used in declaring a variable in the condition of a selection statement (6.4) or an iteration statement (6.5), in the type-specifier-seq in the new-type-id or type-id of a new-expression (5.3.4), in a for-range-declaration, and in declaring a static data member with a brace-or-equal-initializer that appears within the member-specification of a class definition (9.4.2).
A program that uses auto in a context not explicitly allowed in this section is ill-formed.
Once the type of a declarator-id has been determined according to 8.3, the type of the declared variable using the declarator-idWhen a variable declared with a type that uses auto is initialized, or a return statement occurs in a function declared with a return type that uses auto, the deduced return type or variable type is determined from the type of its initializer using the rules for template argument deduction. Let T be the declared typethat has been determined for a variable identifier dof the variable or return type of the function. Obtain P from T by replacing the occurrences of auto with either a new invented type template parameter U or, if the initializer is a braced-init-list (8.5.4), with std::initializer_list<U>. The type deduced for the variabledor return type is then the deduced A determined using the rules of template argument deduction from a function call (14.8.2.1), where P is a function template parameter type and the initializerfor dis the corresponding argument. In the case of a return with no operand, the corresponding argument is void(). If the deduction fails, the declaration is ill-formed. [ Example:— end example ]auto x1 = { 1, 2 }; // decltype(x1) is std::initializer_list<int> auto x2 = { 1, 2.0 }; // error: cannot deduce element typeIf the list of declarators contains more than one declarator, the type of each declared variable is determined as described above. If a function with a declared return type that uses auto has multiple return statements, the return type is deduced for each return statement.
IfIn either case, if the type deduced for the template parameter U is not the same in each deduction, the program is ill-formed. [ Example:The type of i is the deduced type of the parameter u in the call f(expr) of the following invented function template:const auto &i = expr;— end example ]template <class U> void f(const U& u);If a function with a declared return type that uses auto has no 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:
auto f() { } // OK, return type is void auto* g() { } // error, cannot deduce auto* from void()When a declaration that uses auto in its declared type appears as an expression, the type of the expression is the deduced type of the declaration. If the declaration does not yet have a deduced type, the expression is ill-formed. But once a return statement has been seen in a function, the return type deduced from that statement can be used in the rest of the function, including in other return statements. [ Example:
—end example]auto n = n; // error, n's type is unknown auto f(); void g() { &f; } // error, f's return type is unknown auto sum(int i) { if (i == 1) return i; // sum's return type is int else return sum(i-1)+i; // OK, sum's return type has been deduced }Return type deduction for a function template that uses auto in its declared type occurs at instantiation time even if the function body contains a return statement with a non-type-dependent operand. [ Note: So any use of a specialization of the function template will cause an implicit instantiation. —end note ] [ Example:
—end example]template <class T> auto f(T t) { return t; } // return type deduced at instantiation time typedef decltype(f(1)) fint_t; // must instantiate f<int> to deduce return typeRedeclarations or specializations of a function or function template with a declared return type that uses auto must also use auto, not a deduced type. [ Example:
auto f(); auto f() { return 42; } auto f(); // OK int f(); // error, cannot be overloaded with auto f() template <typename T> auto g(T t) { return t; } // #1 template auto g(int); // OK, return type is int template char g(char); // error, no matching template template<> auto g(double); // OK, forward declaration with unknown return type template <class T> T g(T t) { return t; } // OK, not functionally equivalent to #1 template char g(char); // OK, now there is a matching template template auto g(float); // still matches #1 void h() { return g(42); } // error, ambiguous
If a lambda-expression does not include a lambda-declarator, it is as if the lambda-declarator were ().If a lambda-expression does not include a trailing-return-type, it is as if the trailing-return-type denotes the following type:The lambda return type is auto, which is replaced by the trailing-return-type if provided, or otherwise deduced from return statements as described in 7.1.6.4. [ Example:
if the compound-statement is of the form{ attribute-specifier-seqopt return expression ; }the type of the returned expression after lvalue-to-rvalue conversion (4.1), array-to-pointer conver- sion (4.2), and function-to-pointer conversion (4.3);otherwise, void.— end example ]auto x1 = [](int i){ return i; }; // OK: return type is int auto x2 = []{ return { 1, 2 }; }; // OK: return type is std::initializer_list<int>error: the return type is void (a// braced-init-list is not an expression)