116

C++11 supports a new function syntax:

auto func_name(int x, int y) -> int;

Currently this function would be declared as:

int func_name(int x, int y);

The new style does not seem to be widely adopted yet (say in the gcc stl)

However, should this new style be preferred everywhere in new C++11-programs, or will it only be used when needed?

Personally, I prefer the old style when possible, but a code-base with mixed styles looks pretty ugly.

6
  • 36
    It's there mostly for decltype on arguments. Commented Jun 26, 2012 at 20:13
  • what CatPlusPlus says: makes not much sense using it in your example
    – stijn
    Commented Jun 26, 2012 at 20:16
  • 1
    @Cat Plus Plus This means that you leave things as they are in C++03, unless you need to derive the return type?
    – mirk
    Commented Jun 26, 2012 at 20:19
  • 1
    Ugly to have to specify "auto" in front of every function. Is that like C++'s racy answer to python's "def"? Commented Mar 26, 2020 at 2:03
  • I'd only use this when needed (e.g. when using decltype in the return type). I think this is the whole point of this syntax being introduced in the first place. Commented Jan 25, 2021 at 23:03

4 Answers 4

135

There are certain cases where you must use a trailing return type. Most notably, a lambda return type, if specified, must be specified via a trailing return type. Also, if your return type utilizes a decltype that requires that the argument names are in scope, a trailing return type must be used (however, one can usually use declval<T> to work around this latter issue).

The trailing return type does have some other minor advantages. For example, consider a non-inline member function definition using the traditional function syntax:

struct my_awesome_type
{
    typedef std::vector<int> integer_sequence;

    integer_sequence get_integers() const;
}; 

my_awesome_type::integer_sequence my_awesome_type::get_integers() const
{
    // ...
}

Member typedefs are not in scope until after the name of the class appears before ::get_integers, so we have to repeat the class qualification twice. If we use a trailing return type, we don't need to repeat the name of the type:

auto my_awesome_type::get_integers() const -> integer_sequence
{
    // ...
}

In this example, it's not such a big deal, but if you have long class names or member functions of class templates that are not defined inline, then it can make a big difference in readability.

In his "Fresh Paint" session at C++Now 2012, Alisdair Meredith pointed out that if you use trailing return types consistently, the names of all of your functions line up neatly:

auto foo() -> int;
auto bar() -> really_long_typedef_name;

I've used trailing return types everywhere in CxxReflect, so if you're looking for an example of how code looks using them consistently, you can take a look there (e.g, the type class).

5
  • 1
    It does not look like there is a consensus yet, but it is interesting to look at CxxReflect with the new style.
    – mirk
    Commented Jun 27, 2012 at 8:22
  • Hi, James. This answer could probably be made more accurate in light of the C++14 standard. Commented Jan 15, 2015 at 3:09
  • @DrewDormann What would you add/change? Commented Oct 26, 2018 at 21:08
  • 1
    Alignment is actually a big plus, to the point i wished there would be a new 'func' keyword to replace the meaningless 'auto' here. Commented Jan 2, 2019 at 9:36
  • 4
    #define fn auto and you can write code looking like Rust ;-) Commented Feb 5, 2020 at 10:13
77

In addition to what others said, the trailing return type also allows to use this, which is not otherwise allowed

struct A {
  std::vector<int> a;

  // OK, works as expected
  auto begin() const -> decltype(a.begin()) { return a.begin(); }

  // FAIL, does not work: "decltype(a.end())" will be "iterator", but 
  // the return statement returns "const_iterator"
  decltype(a.end()) end() const { return a.end(); }
};

In the second declaration, we used the traditional style. However since this is not allowed at that position, the compiler does not implicitly use it. So the a.end() uses the statically declared type of a to determine what end overload of vector<int> it is going to call, which ends up being the non-const version.

1
  • 4
    While this is a good/neat demonstration of the concept (using members in return types), it's funny since in C++14 specifying a type is totally redundant in an inline definition with no conversion; we can now just use full return type deduction. :P Commented Oct 26, 2018 at 21:12
41

Another advantage is that the trailing-return-type syntax can be more readable when the function returns a pointer to a function. For example, compare

void (*get_func_on(int i))(int);

with

auto get_func_on(int i) -> void (*)(int);

However, one can argue that better readability can be achieved simply by introducing a type alias for the function pointer:

using FuncPtr = void (*)(int);
FuncPtr get_func_on(int i);
1
  • 4
    I personally always preferred using Func = void(int); Func* get(); keep the pointer nature obvious – if at all, as not too much a friend of spoiling the type library just for a single occurrence. In the latter case, the trailing RT comes in handy!
    – Aconcagua
    Commented Oct 6, 2022 at 10:36
11

See this nice article: http://www.cprogramming.com/c++11/c++11-auto-decltype-return-value-after-function.html Very good example when to use this syntax without decltype in game:

class Person
{
public:
    enum PersonType { ADULT, CHILD, SENIOR };
    void setPersonType (PersonType person_type);
    PersonType getPersonType ();
private:
    PersonType _person_type;
};

auto Person::getPersonType () -> PersonType
{
    return _person_type;
}

And brilliant explanation also stolen from Alex Allain's article "Because the return value goes at the end of the function, instead of before it, you don't need to add the class scope."

Compare to this possible case when one by accident forget about class scope, and, for bigger disaster, another PersonType is defined in global scope:

typedef float PersonType; // just for even more trouble
/*missing: Person::*/
PersonType Person::getPersonType ()
{
    return _person_type;
}
3
  • 8
    I'm not sure that this falls into the "disaster" category: if the type is wrong, the code won't compile. Runtime errors can have disastrous consequences; compile-time errors, not so much. Commented Jun 26, 2012 at 23:59
  • 5
    @JamesMcNellis compare the compiler output: prog.cpp:13:12: error: prototype for 'PersonType Person::getPersonType()' does not match any in class 'Person' vs. prog.cpp:13:1: error: 'PersonType' does not name a type The first error from compiler is, at least for me, worse to understand.
    – PiotrNycz
    Commented Jun 27, 2012 at 7:37
  • 1
    Personally I don't agree, I find the second message harder to read, and I'd rather have the implementation look like the declaration.
    – jrh
    Commented Aug 19, 2020 at 18:58

Not the answer you're looking for? Browse other questions tagged or ask your own question.