What are the rules, that allow writing automatic return types in c++1y ?
#include <iostream>
using namespace std;
template<typename T1, typename T2>
auto f(T1 const& a, T2 const &b)
{
if (a > b) return a-b;
else return a+b;
}
int main()
{
cout << f(1, 2.) << endl;
return 0;
}
Is there a limit imposed by the cyclomatic complexity of a function's body?
Is there a limit imposed by the cyclomatic complexity of a function's body?
Let
T
be the declared type of the variable or return type of the function. If the placeholder is theauto
type-specifier, the deduced type is determined using the rules for template argument deduction. If the deduction is for areturn
statement and the initializer is a braced-init-list (8.5.4), the program is ill-formed. Otherwise, obtainP
fromT
by replacing the occurrences ofauto
with either a new invented type template parameterU
or, if the initializer is a braced-init-list, withstd::initializer_list<U>
. Deduce a value forU
using the rules of template argument deduction from a function call (14.8.2.1), whereP
is a function template parameter type and the initializer is the corresponding argument. If the deduction fails, the declaration is ill-formed. Otherwise, the type deduced for the variable or return type is obtained by substituting the deducedU
intoP
.
So, tl;dr: the return type is deduced from the expression in the return
statement via template argument deduction. There is an imaginary template which is called with the expressions in the return
statements as function arguments, and the deduced template argument U
will be the replacement for the auto in the placeholder return type.
Now, what happens if we have more than one return statement? Simple: We deduce for every return
statement, and check whether they are compatible:
If a function with a declared return type that contains a placeholder type has multiple
return
statements, the return type is deduced for eachreturn
statement. If the type deduced is not the same in each deduction, nthe program is ill-formed.
So, for this code:
template<typename T1, typename T2>
auto f(T1 const& a, T2 const &b)
{
if (a > b) return a-b;
else return a+b;
}
The following deduction is done:
template<typename U>
void g(U);
g( a-b );
g( a+b );
// here, a and b have the exact same types as in a specialization of the template above.
If, and only if, in both calls the same template argument is deduced, the code is well-formed. Otherwise, the deduction fails. If the return type you set with the auto specifier is not a simple auto
but for example auto const&
, the parameter of the imaginary template g
has the corresponding form:
template<typename U>
void g(U const&);
And the calls will be the same. Again, if the deduced U
s differ, the code is ill-formed.
In case you have no return statement, the deduced return type will be void
, according to
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 areturn
statement with no operand at the closing brace of the function body.
It gets more tricky if you want recursive functions:
auto f( int a, int b )
{
return a? b + a : f(a-1, b); // This is ill-formed!
}
The problem is explained by the following quote:
If the type of an entity with an undeduced placeholder type is needed to determine the type of an expression, the program is ill-formed. Once a
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 otherreturn
statements.
So instead we write:
auto f( int a, int b )
{
if( a )
return b + a;
return f(a-1, b);
}
You can use arbitrarily complex functions, as long as the return
statements all yield the same type during deduction and recursive functions have the recursive calls after some non-recursive return
-statements. Cast if necessary to get the same types.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With