When is the automatic return type applied?

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 ab; else return a+b; } int main() { cout << f(1, 2.) << endl; return 0; } 

Is there a restriction imposed by the cyclomatic complexity of a function body?

+6
source share
2 answers

Introduction

There are some simple rules that determine when the return type of a function can be inferred from the body of the function, and with this, when auto applicable as the return type.

All these rules are specified in the standard ( n3797 ) [1] and each rule listed in this section in the remaining parts of this message.


[1] in section 7.1.6.4, the auto-specifier [dcl.type.elab] .


Is there something that cannot be deduced using auto as the return type?

[dcl.type.elab]p1 If the residue is intended for the return , and the initializer is the bit-init-list (8.5.4), the program is poorly formed.

 auto func () { return {1,2,3}; } // ill-formed 



What type will be inferred if the function has more than one return statement?

[dcl.type.elab]p9 If a function with a declared return type containing a placeholder type has several return statements, the return type is displayed for each return statement. If the inferred type is not the same in each output, the program is poorly formed.

 auto gunc_1 (bool val) { // (1), ill-formed if (val) return 123; else return 3.14f; } 

 auto gunc_2 (bool val) { // (2), legal if (val) return static_cast<float> (123); else return 3.14f; } 

Note : (1) is poorly formed, since all return statements are not of the same type, while (2) is legal, because the two returned statements give the same type.



What happens if a function does not have a return statement?

[dcl.type.elab]p10 If a function with a declared return type using a placeholder does not have return statements, the return type is inferred as if from a return statement without an operand in the closing bracket of the function body.

 auto hunc () { } // legal, return-type is `void` 



Can I use this function before the return type is deduced?

[dcl.type.elab]p11 If an object type with an undefined placeholder type is needed to determine the type of an expression, the program is poorly formed. However, once a return statement has been seen in a function, the return type inferred from this statement can be used in the rest of the function, including other return statements.

 auto junc (); // declaration void foo () { &junc; } // (1), ill-formed auto junc () { // definition return 123; } void bar () { &junc; } // (2), legal 

 auto recursive (int x) { if (--x) return x + recursive (x); // (3), ill-formed else return 0; } 

Note We cannot take the junc address inside foo , since this requires knowledge of what the full junc type is, what is not known until we present a definition in which the return type is deduced. (2) is legal, while (1) is not.

Note : (3) is also poorly formed, since at this moment we should know the recursive return type, but it is not known. However, have converse statements in reverse order. That way, the compiler would know recursive to return an int when it reaches return x + recursive (x) .

+3
source

Is there a restriction imposed by the cyclomatic complexity of a body function?

What the standard indicates (N3797, Β§7.1.6.4):

Let T be the declared type of the variable or the return type of the function. If the placeholder is an auto type specifier, the inferred type is determined using the rules for outputting the template argument. If the output is for the return , and the initializer is braced-init-list (8.5.4), the program is poorly formed. Otherwise, get P from T by replacing the occurrences of auto either the newly invented template parameter of type U or, if the initializer is braced-init-list, with std::initializer_list<U> . Derive the value for U using the rules for deriving a template argument from a function call (14.8.2.1), where P is the type of the function template parameter, and the Initializer is the corresponding argument. If the deduction fails, the declaration is poorly formed. Otherwise, the type deduced for the variable or return type is obtained by substituting the deduced U in P

So tl; dr: the type of the return value is inferred from the expression in the return expression by subtracting the template argument. There is an imaginary template that is called with expressions in the return as function arguments, and the computed argument of the U template will replace the auto in the return type. Now, what happens if we have more than one return statement? Simple: we print return for each return and check if they are compatible:

If a function with a declared return type containing a placeholder type has several return , a return type is output for each return . If the inferred type is not the same in each subtraction, the program is poorly organized.

So for this code:

 template<typename T1, typename T2> auto f(T1 const& a, T2 const &b) { if (a > b) return ab; else return a+b; } 

The following conclusion is made:

 template<typename U> void g(U); g( ab ); g( a+b ); // here, a and b have the exact same types as in a specialization of the template above. 

If and only if the same template argument is output in both calls, the code is correctly generated. Otherwise, the output is not executed. If the return type that you set using the automatic specifier is not 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 challenges will be the same. Again, if the output U is different, the code is poorly formed.

If you do not have a return statement , the return type returned will be void , according to

If a function with a declared return type using a placeholder does not have return , the return type is deduced from a return without an operand in the closing bracket of the function body.

recursion

This gets more complicated 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 an object type with an unconfirmed placeholder type is required to determine the type of expression, the program is poorly formed. However, once a return been seen in a function, the return type inferred from this statement can be used in the rest of the function, including in other return operations.

So instead we write:

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

Output:

You can use arbitrarily complex functions if the return all return the same type during the output, and the recursive functions have recursive calls after some non-recursive return statements. If necessary, cite the same types.

+7
source

Source: https://habr.com/ru/post/970119/


All Articles