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:

auto foo() -> int; //same as int foo();

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

auto bar() {
  return 42; //return type deduced as int
}

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.

template <class T>
auto addFooAndBar(T const& t) -> decltype(t.foo() + t.bar()) {
  return t.foo() + t.bar();
}

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:

class FizzAndBuzzCreator {
public:
  Fizz foo();
  Buzz bar();
};

FizzBuzz operator+(Fizz const& f, Buzz const& b);

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:

class HasAContainer {
  typedef std::vector<int> container_t;

  container_t values;
public:
  auto begin() const {
    return std::begin(values);
  }
  auto itemAt(container_t::size_type index) const {
    return values[index];
  }
  //...
};

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:

template <class T>
auto addFooAndBar(T const& t) {
  return t.foo() + t.bar();
}

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 *