Trailing return types everywhere

Trailing return types are an oddity in C++ – we should use them only when necessary.

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.

Facebooktwittergoogle_plusredditlinkedinFacebooktwittergoogle_plusredditlinkedinby feather

5 Comments


  1. This post starts very definitively by saying we should only use trailing return when necessary, but the conclusion is that you are undecided.

    Saying a feature should be avoided because others may not be familiar with it is a great way to ensure the community using a language never evolves.

    Personally, I’ve switched to always using trailing returns because it vertically aligns the function names; and yes you get the technical benefits you mentioned as well.

    I absolutely love static type systems, but I feel names are much more important than types when trying to convey meaning to readers of my code. The trailing return type syntax puts more emphasis on the name of the function first. If a reader cares about the details of what type is returned, they can keep reading the line to learn more.

    Personally, I wish we could have a syntax to declare variables/parameters with their name first followed by the type for the same reasons.

    Reply
    1. Chris

      That argument won me over when somebody on StackOverflow used it, but after actually trying it out on a new personal project, I noticed that aligning the function names that perfectly looked more readable to me only when the functions took one line each.

      In general, unless a return type is particularly long or there are a bunch of one-line functions together, I find it more readable to have the return type first, perhaps because it’s what I’m used to. That said, I like the idea of a foo(a: T1, b: T2): T3 { ... } syntax found in other languages, so I really have no clue why a trailing return type doesn’t appeal to me as much. Maybe it’s the extra auto shrugs

      Reply

      1. The trailing return mostly helps in headers. I’m not often writing library code (read: templates), so I have very little inline function definitions in header files. It’s so much nicer (IMO) seeing all the function names line up without needing grotesque amounts of whitespace like some people do.

        That said, for consistency with the declarations in the header, I use trailing return in the associated .cpp files as well, even though the benefits there are minimal.

        And yeah it would be nice if the grammar allowed us to omit the leading auto to use trailing return syntax; would probably only be possible if we had a fun(ction) keyword.

        Reply

  2. I find trailing return type particularly useful when I am likely to revisit code later. C style return value may cause unintended implicit type conversions. -> type is an inline reminder of my expectation from compiler’s type deduction for past iteration’s code. Without such visibility, the overhead of maintaining more trivial tests and revising lengthy documentation grows.

    Reply
  3. rhalbersma

    You can force auto-return by reference using auto& or auto const&. Or, in generic code, you can let the compiler deduce the referenceness by using decltype(auto).

    A disadvantage of return type deduction without trailing return type in public API functions is that documentation tools do not yet fill in the return type for you. Maybe some clang-based tools might be able to in the future. Also, you can’t mix auto returns and explicit return types between forward declarations and definitions. That said, I use auto return in every non-API function.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *