Modern C++ Features – auto for Functions

A few posts ago I have written about auto for variables. This time will be about the same keyword for a different use: auto as return type for functions.

auto for functions comes in two different flavors. In C++11 it was introduced to be able to declare the return type of a function after its parameter list, like this:

With C++14 the standard introduced the possibility of return type deduction, which had already been possible for lambdas in C++11:

Trailing return types

The C++11 form does not give us much at first sight. We still have to declare the return type, but compared to a traditional function declaration we have to add auto and ->. In addition, the function declaration looks odd this way, if you are used to the original syntax.

So why use this form at all? It could come in handy when the return type depends on the parameter types, especially in template functions where you don’t know exactly the types you get when you apply certain operations to that type.

This function will return the sum of whatever the foo() and bar() member functions return. If they both return an int, the return type of addFooAndBar will be int, too. But consider this example:

Now the return type of addFooAndBar<FizzAndBuzzCreator> will be a FizzBuzz.

Return type deduction

For the C++14 form of auto with functions, the compiler can deduce return types for any function, no matter how complex. The only condition is that each return statement must have the exact same type. The rules then are the same as for auto variables.

To be able to deduce the type, the compiler needs to see the function definition right ahead. That means, this use is restricted to inline functions, function templates and helper functions that are used only inside a single translation unit.

For a “normal” function that is declared in a header abut implemented elsewhere it is not applicable. However, templates, inline functions and helper functions are enough places where you can and should use return type deduction.

I say should, because like for variable type deduction function return type deduction it avoids unnecessary and unwanted conversions and the ripple of type changes you have to apply. Change the type of a single variable and the return types of the functions using it will change along:

Maybe vector is not the right container type? Change it – the iterator type returned by begin will change, too. Want to store long long instead of int? No problem, change values to vector<long long>, and the return types of begin and itemAt will be deduced to the right types.

With return type deduction, most use cases of trailing return types are obsolete. Our example above now can just be written like this:

As well as the compiler, the reader of a function with return type deduction should be able to see the return statements together with the function header. This in turn means your functions should be short – but of course this applies to any function, not only those with return type deduction.

Conclusion

If technically possible, there is no reason to avoid return type deduction. On the contrary, it can help to make the types you use more consistent.

Prefer to use function return type deduction wherever applicable.

Trailing return types on the other hand are necessary only seldom – if at all – and are awkward due to their unusual syntax.

Avoid trailing return types unless you really need them. They make your code harder to read.

Facebooktwittergoogle_plusredditlinkedinFacebooktwittergoogle_plusredditlinkedinby feather

4 Comments

  1. A. Duret-Lutz

    The use of values::size_type in the prototype of HasAContainer::itemAt seems incorrect to me.

    Reply
    1. Arne Mertz

      Thanks for pointing it out. Fixed.

      Reply

Leave a Reply

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