Trailing return types are an oddity in C++ – we should use them only when necessary . Decide carefully whether to adopt them as a general style, and try to stay consitent.

Update: there is a follow-up post to this. Take this post with a grain of salt!

A few days ago one of my coworkers asked me to explain an odd line of code he had encountered in an open source library. The line was similar to this:

auto getMulticastHops() const -> int;

Some people will know that this is a way of declaring functions that came into the language with C++11. The part -> int is called “trailing return type”, and the line is exactly the same as

int getMulticastHops() const;

Why have trailing return types?

There were good reasons to introduce trailing return types in C++11. The standard examples are function templates where the return type depends on the argument types. Let’s, for example, take a function that multiplies two values:

template<class T, class U> auto multiply(T const& lhs, U const& rhs) -> decltype(lhs * rhs) { return lhs * rhs; }

One could, of course, use std::declval to not have to use trailing return types:

template<class T, class U> decltype(std::declval<T>() * std::declval<U>()) multiply(T const& lhs, U const& rhs) { return lhs * rhs; }

As you see, this gets cluttered and barely readable very quickly. Another example is lambdas, where the “normal” way to declare return types is not allowed by the syntax.

What about return type deduction?

C++11 lambdas already had return type deduction for the easier cases. C++14 added it for the more general cases and for normal functions as well. The above function can be written simply like this:

template<class T, class U> auto multiply(T const& lhs, U const& rhs) { return lhs * rhs; }

Return type deduction can help in many cases where trailing return types were necessary before, but not everywhere. For example, the compiler will always deduce return by value, never by reference. So if you want to return by reference from a lambda, there is no way around using trailing return types.

Other cases where return type deduction is impossible are of course function declarations without a body – these will however never be lambdas.

Trailing return types everywhere?

It might be tempting to use trailing return types everywhere now. Some people also argue that they make our code more consistent. Others use them because they have to be used in some places anyway, but many will also use them because they are new and new is cool. We get this a lot with new features and possibilities. They get hyped and sometimes overused.

I am not saying that we should not decide to switch to “Always Auto for Functions” – but if we do, we should do it for the right reasons. Keep in mind that there also are reasons that speak against that style:

There are billions of lines of old C++ code out there using the classic return type style. Keeping that in mind, “consistency” might be a less compelling argument for trailing return types.

There are lots of programmers out there who are not yet familiar with trailing return types. That may be C++ programmers, but also programmers coming from other languages. Using trailing return types everywhere might pose an additional hurdle for those programmers to get familiar with your code.

I have to confess, I am somewhat undecided on the matter. I’ll probably stick to the classic style for the time being and see for which style the community will settle in the next years.

Please check out the follow-up post also.