Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When can automatic return type apply? [duplicate]

Tags:

c++

c++14

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?

like image 285
Nikos Athanasiou Avatar asked May 31 '14 09:05

Nikos Athanasiou


1 Answers

Is there a limit imposed by the cyclomatic complexity of a function's body?

What the standard specifies (N3797, §7.1.6.4):

Let T be the declared type of the variable or return type of the function. If the placeholder is the auto type-specifier, the deduced type is determined using the rules for template argument deduction. 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, with std::initializer_list<U>. Deduce a value for U 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 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 deduced U into P.

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 each return 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 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 return statements, the return type is deduced as though from a return statement with no operand at the closing brace of the function body.

recursion

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 other return statements.

So instead we write:

auto f( int a, int b )
{
    if( a )
        return b + a;

    return f(a-1, b);
}

Conclusion:

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.

like image 140
Columbo Avatar answered Sep 28 '22 00:09

Columbo