Jason Merrill
2013-03-15
Revision 3
N3582

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++14. 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).

ChangeLog

Revision 2: N3386
Revision 3: N3582 Changes since N3386 are in blue.

Redeclaration

Allowing non-defining function declarations with auto return type is not strictly necessary, but it is 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.

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

SFINAE

Since the return type is deduced by instantiating the template, if the instantiation is ill-formed, this causes an error rather than a substitution failure. This allows an auto function to return a lambda, which is not possible using the decltype(returned expression) pattern.

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.

Therefore, I propose to also allow decltype(auto) to get the decltype semantics without having to repeat the expression. For simplicity of specification and orthogonality I propose to allow it everywhere that plain auto is allowed, except for introducing a trailing-return-type. It occurs to me that the difference in meaning of decltype depending on the form of the expression (e.g. parenthesized or not) might be more surprising in this context, but I think it would be even more surprising if this worked differently from C++11 decltype.

noexcept

A few people have requested that an auto function should have a deduced noexcept-specifier as well as return type. I don't think that tying the two deductions together is appropriate, but if people want general deduction of noexcept-specifiers, EWG might wish to revisit N3207.

auto in trailing-return-type

This proposal initially did not allow auto in a trailing-return-type, but since then it was pointed out that putting it there is the only way to specify that a lambda returns by a deduced reference type:
[]()->auto& { return f(); }

Deducing std::initializer_list

Although a variable declared auto and initialized with a brace-enclosed initializer list gets a type of std::initializer_list, we don't want that for a function return type; since the underlying array is allocated on the stack, it would immediately leak on return, and any use of the return value would have undefined behavior.

Proposed wording

Change 7.1.6.2¶1:
decltype-specifier:
decltype ( expression )
decltype ( auto )
Change 7.1.6.2¶4:
The type-specifier decltype(auto) indicates a type to be deduced as for the auto type-specifier (7.1.6.4), except that the deduced type is determined using the following rules rather than template argument deduction. For an expression e, The type denoted by decltype(e) is defined as follows: The operand of the decltype specifier is an unevaluated operand (Clause 5). [ Example:
const int&& foo();
int i;
struct A { double x; };
const A* a = new A();
decltype(foo()) x1 = i;   // type is const int&&
decltype(auto) x1a = foo(); // type is const int&&
decltype(i) x2;           // type is int
decltype(a->x) x3;        // type is double
decltype((a->x)) x4 = x3; // type is const double&
—end example ]
Change 7.1.6.4:
The auto and decltype(auto) type-specifiers designate 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 placeholder type can appear with a function declarator in the decl-specifier-seq, type-specifier-seq, conversion-function-id, or trailing-return-type, 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. If the declared return type of the function contains a placeholder type, 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 or decltype(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 or decltype(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 A placeholder type 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 or decltype(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 using a placeholder type is initialized, or a return statement occurs in a function declared with a return type that contains a placeholder type, the deduced return type or variable type is determined from the type of its initializer. In the case of a return with no operand, the initializer is considered to be void(). Let T be the declared type of the variable or return type of the function.

If the placeholder corresponds to the auto type-specifier, the deduced type is determined using the rules for template argument deduction. Let T be the declared type that has been determined for a variable identifier d. If the deduction is for a return statement and the initializer is a braced-init-list (8.5.4), the program is ill-formed. Otherwise, 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>. If 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. 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 placeholder corresponds to the decltype(auto) type-specifier, the deduced type is determined as though the initializer had been the operand of the decltype. [ Example:

int i;
int& f();
decltype(auto) x3 = i;        // decltype(x3) is int
decltype(auto) x4 = (i);      // decltype(x4) is int&
decltype(auto) x5 = f();      // decltype(x5) is int&
decltype(auto) x6 = { 1, 2 }; // error, { 1, 2 } is not an expression
— 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 contains a placeholder type 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 a placeholder type 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 with a placeholder 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 with a placeholder 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 a placeholder type must also use that placeholder, not a deduced type. [ Example:

auto f();
auto f() { return 42; } // return type is int
auto f(); // OK
int f();  // error, cannot be overloaded with auto f()
decltype(auto) f(); // error, auto and decltype(auto) don't match

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: The lambda return type is auto, which is replaced by the trailing-return-type if provided and/or 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 }; }; // error: deducing return type from braced-init-list the return type is void (a
                                  // braced-init-list is not an expression)
int j;
auto x3 = []()->auto&& { return j; }; // OK: return type is int&
— end example ]

References

Crowl, Lawrence and Alisdair Meredith. N2954: Unified Function Syntax (and draft revision)