Why do we need this syntax, because the result type is not calculated by the automaton?

auto foo(int arg) -> int {}

    3 answers 3

    This syntax is the result of the inclusion in the C ++ standard of lambda expressions.

    A lambda expression can be written, for example, like,

     auto foo = [](int arg) -> int { /*...*/ }; 

    This lambda expression can be converted to a function of type int( int )

    This syntax was adopted to declare functions. The function must have a specifier (s) of the type of the returned expression at its beginning. Since the actual type of the return value is indicated after the list of parameters, the auto specifier is used as the specifier of the return value in the function declaration.

    In the example you cited, it makes no sense to declare a function this way. But sometimes the type of the return value may depend on the type of evaluation of a complex expression, which can be difficult for a programmer to determine on his own, and this can lead to an error.

    Therefore, this syntax is convenient, for example, when declaring template functions.

    Consider the following demo program.

     #include <iostream> template <class T, class U> auto foo(const T &x, const U &y) -> decltype( x + y ) { return x + y; } int main() { int x = 10; int y = 20; std::cout << typeid(foo(x, y)).name() << std::endl; long z = 30; std::cout << typeid(foo(x, z)).name() << std::endl; float f = 40; std::cout << typeid(foo(x, f)).name() << std::endl; } 

    The output of the program to the console, for example, in MS VC ++ may look like

     int long float 

    It is impossible to say in advance what the type of the returned expression will be. It depends on the types of the parameters of the function and the type of the expression being evaluated. Using the auto type specifier in this example makes it easy to declare a function.

      • It is needed, in particular, in order to place the return type in the context where the function parameter names are visible, respectively, their semantics are known. for example

         auto foo(short a, short b) -> decltype(a + b) { ... } 

        In this case, the compiler will take into account the fact that the operands of the expression a + b will be subject to integral promotions and [on most platforms] will give the result of type int . At the same time, on platforms where integral promotions for short give unsigned int the return type of the function will automatically become unsigned int . (Such "flexibility" is desirable or not - depends on the context.)

      • Also, when defining a class method outside the class definition, this type is considered to be in the scope of the class, which affects the process of searching for unqualified names.

         typedef void *Ret; struct A { typedef int Ret; Ret foo(); Ret bar(); }; auto A::foo() -> Ret { return 0; } // OK, `Ret` обозначает именно `A::Ret` Ret A::bar() { return 0; } // Oшибка - `Ret` обозначает `::Ret` 

        When using the "classic" syntax, the definition would have to specify a qualified name of type A::Ret .

        Especially significant syntax with -> makes it easy to write when defining methods of a template class outside the class, because the qualified name of the template class can be very long, and also require the indication of the keyword typename .

      • When using this syntax, you get the opportunity to use the "compact" notation of complex types (similar to how it is done in using instead of typedef )

         auto foo() -> int (*)[20] { ... } 

        In a “classic” recording, it would look like

         int (*foo())[20] { ... } 

        that many will find it less readable.

      • So why not write decltype ahead? - user239213
      • 2
        @ user239213: "Ahead" you will not have access to the name arg . - AnT

      Well, first of all for templates. The compiler may not know the return type before seeing the argument? For example,

       template<class T> ??? f(const T& t) { return t*5.0; } 

      What is there to put in place ??? ? What if T is a class with an overridden multiplication operator? which returns an object of another class?

      That's how

       template<class T> auto f(const T& t) -> decltype(t*5.0) { return t*5.0; } 

      no problem...

      • C #, for example, copes well: T MaxBy<T>(IEnumberable<T>seq, Func<T, double> selector) . - VladD
      • @VladD But we are talking about C ++ :) - Harry
      • Well, since C # can spy ahead and understand what T , then C ++ could spy ahead and parse a decltype(t*5.0) f(const T& t) type declaration correctly. - VladD
      • @VladD in my opinion your example is not indicative. Because T and decltype(t*5.0) can be of different types. - αλεχολυτ
      • @VladD: The historically established rules of the name lookup of the C ++ language require that in such a situation the compiler should look back , not forward. Those. in int t; template<class T> decltype(t*5.0) f(const T& t) {} int t; template<class T> decltype(t*5.0) f(const T& t) {} compiler must find exactly the previous int t; . Therefore, the transition to "looking ahead" in this case does not boil down to a simple "could have a look", but is a fundamental alteration of the rules of the name lookup language, which turns everything upside down. - AnT