Trailing return types everywhere

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.

Previous Post
Next Post

16 Comments


  1. Personally I dislike auto because I want to know whats coming back and templates because I hate having functions defined in header files – if you want to go that route put all the functions in the header file – I dont want to keep swapping files to see whats going on. To be perfectly honest the more I see of ‘modern c++’ the more I hate it and the more I want to go back to C. Lots of these ‘its safer like this’ seem to me to be holding my hand as if I were a baby, I have written code for a long time I dont need to be told I cant use arrays because I might go out of bounds or cant cast. If you want then try and make your code checking software intelligent enough to really check. One day I promse myself I will find the time to write a C and a modern C++ version of the same reasonable size code just to prove why modern machines have multiple 3ghz processors but do a dir command slower than my 6mhz single core 286

    Reply

    1. Obfuscation of code through lack of explicitness is a valid point.
      Please ping me when you’re done with the C vs. C++ comparison. I’m interested especially in the run time performance aspect you mention. Unless your example is specifically constructed to let C++ look bad I don’t see where a difference that late would come from. Happy to learn though.

      Reply


  2. Everyone says the Most Vexing Parse is solved by uniform initializers, but that doesn’t completely remove possible confusion caused by function declarations that still look like initializers, combined with the fact that there are still legitimate needs for () initializers (the vector example). It surprises me a little that nobody advocates for trailing return type simply to help make all function syntax declaration less ambiguous.

    Reply

  3. I would like to know the following:
    If I write pointer to a function taking an int as a paramter and returning int as follows:

    auto (*pfunc)(int) -> int;

    Well, then how can I write the array of such pointers of 10 elements.?
    Be advised that such approach as

    auto (*pfunc[10])(int) -> int;

    does not work.

    Reply

    1. Function pointer syntax is weird and not easy to read. I’d never use it to directly declare an array of them. Instead, define a type alias to the function pointer type and then declare an array of the alias.

      Reply

  4. And there is decltype(auto) for return type if we want to deduce value or reference:

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

    Reply

    1. Does this eliminate the need for tailing return types in all cases?

      Reply

      1. It doesn’t. You still need to provide a return type for function declarations. Return your deduction with auto and decltype(auto) is only possible for function definitions.

        Reply

  5. I just like the alignment of:

    void method ();
    bool method ();
    auto method () -> type;

    Reply

  6. Trailing return type seems to be a feature of several newer languages (eg. Swift, Rust). As these languages pick up steam I expect we’ll be seeing more consistent use of the syntax in C++ as well.

    Reply

  7. 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. 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

  8. 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

  9. 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 *