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
Tbe the declared type of the variable or return type of the function. If the placeholder is theautotype-specifier, the deduced type is determined using the rules for template argument deduction. If the deduction is for areturnstatement and the initializer is a braced-init-list (8.5.4), the program is ill-formed. Otherwise, obtainPfromTby replacing the occurrences ofautowith either a new invented type template parameterUor, if the initializer is a braced-init-list, withstd::initializer_list<U>. Deduce a value forUusing the rules of template argument deduction from a function call (14.8.2.1), wherePis 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 deducedUintoP.
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
returnstatements, the return type is deduced for eachreturnstatement. 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 Us 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
returnstatements, the return type is deduced as though from areturnstatement 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
returnstatement 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 otherreturnstatements.
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