Jason Merrill
2012-03-27
Revision 2
N3386=12-0076
Return type deduction for normal functions
Introduction
Any C++ user introduced to the C++11 features of auto, lambdas,
and trailing return types immediately wonders why they can't just
write auto on their function declaration and have the return type
deduced. This functionality was proposed previously in
N2954,
but dropped from C++11 due to time constraints, as the drafting didn't
address various questions and concerns that the Core WG had. I have now
implemented this functionality in GCC, and propose to add it to C++1y. I
discuss some of the less obvious aspects of the semantics below.
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).
Redeclaration
I am of two minds about allowing non-defining function declarations with
auto return type. On the one hand, it would be useful for coding styles
that prefer to define member functions outside the class:
struct A {
auto f(); // forward declaration
};
auto A::f() { return 42; }
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:
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
But then again, we already have the same situation if f returns an
incomplete class type which is defined between h1 and h2.
The proposed wording allows non-defining declarations so long as all
declarations have the same declared type, without considering the deduced
type.
auto f(); // return type is unknown
auto f() { return 42; } // return type is int
auto f(); // redeclaration
int f(); // error, declares a different function
And similarly for templates:
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:
auto f(); // return type is unknown
int i = f(); // error, return type of f is unknown
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.
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
Multiple returns
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.
auto iterate(int len) // error, body isn't "return expr;"
{
for (int i = 0; i < len; ++i)
if (search (i))
return i;
return -1;
}
Both return statements here return int, we can determine that perfectly
well. The proposed wording allows this example, and resolves core issue
975.
Recursion
One important difference between lambdas and normal functions is that
normal functions can refer to themselves by name. Of course, we can't
deduce the return type that way:
auto h() { return h(); } // error, return type of h is unknown
but once we have deduced a return type, there is no reason to prohibit recursion.
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
}
Instantiation
Like constexpr functions, it can be necessary to instantiate
an auto function template even if it is not odr-used.
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
More general deduction
The declarator of a variable declared with auto is not limited in
the form of the declarator; the same should be true of a function with
deduced return type. In particular, this is the only way to deduce return
by reference:
template <class T> struct A { static T t; };
template <class T> auto& f() { return A::t; } // returns by reference
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.
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
Difference from decltype
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.
Proposed wording
Change 7.1.6.4:
The auto type-specifier designates a placeholder type that will be
replaced later, by either deduction from an initializer
or signifies that the type of a variable being declared shall be
deduced from its initializer or that a function declarator shall
include a trailing-return-type.
The auto type-specifier may can appear with a
function declarator either in the decl-specifier-seq
or type-specifier-seq or as part
of a conversion-function-id with 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 variable The 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:
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 defined
— end example ]
The 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-id When 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 type that 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 variable d or 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 initializer for d is 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:
auto x1 = { 1, 2 }; // decltype(x1) is std::initializer_list<int>
auto x2 = { 1, 2.0 }; // error: cannot deduce element type
— end example ]
If 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. If In
either case, if the type deduced for the template parameter U is not
the same in each deduction, the
program is ill-formed.
[ Example:
const auto &i = expr;
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);
— end example ]
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:
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
}
—end example]
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:
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
—end example]
Redeclarations 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
Change 5.1.2¶4:
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:
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.
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:
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)
— end example ]
References
Crowl, Lawrence and Alisdair Meredith. N2954: Unified Function Syntax (and draft revision)