1. Overview

In this article, we will be talking about C++14 new ways to specify the return type. We will also go deeper into the problem by explaining how function template type deduction is related to auto and when decltype(auto) can be used in its place.

2. auto type deduction before C++14

Before diving into this entry’s actual title, let’s quickly skim through the type deduction present in C++ before C++14. Starting with C++11, programmers can use auto instead of explicitly specifying the variable type. auto type deduction is almost the same as template type deduction, which is used in function templates to deduce its parameter types. Almost – because it deduces type differently when braces are used (more on that below). What does it mean that auto uses (almost) the same rules as for template type deduction? It means that function template type deduction can be visualized as a variable with auto type deduction:

template<typename T> void foo(T some_param);
foo(123); // here T is deduced as int

auto some_variable = 123; // here auto resolves to int and is being used in the same role as T above

You may add various adornments, like const, &, or * and the translation still happens as before:

template<typename T> void foo(const T& some_param);
int k;
foo(k); // here T is still deduced as int

int p;
const auto& some_variable = p; // here auto resolves to int as before

as you can see, const and & are surrounding T and auto and has the same meanings.

The only difference is when using brace initialization, auto will deduce std::initializer_list<> but template type deduction will show error:

template<typename T> void foo(T some_param);
foo({1,2,3}); // does not compile

auto some_variable = {1,2,3}; // type of some_variable is std::initializer_list<int> with contents: 1,2,3

Starting with C++11 you may use decltype to infer the type of return value, but it is not the same as the C++14 auto return type deduction. It looks as follows:

auto foo(int n) -> decltype(n) {
  return n*n;
}

here auto only informs the compiler that we want to use the trailing return type compiler feature, and the return type will be available after the parameter list with the use of decltype type deduction rules.

3. C++14 and new auto return type deduction

Now, to the point. With C++14 we can use auto to deduce the return type of functions. Previously in C++11, this was possible only in lambdas. Each return expression should deduce to the same type. The important thing is that this deduction is using template type deduction (as above in function template type deduction) and not the one as in auto type deduction for variables:

auto foo() {
  return 123; // fine, foo returns int, its signature is int()
}

auto bar() {
  return {1,2,3}; // error: cannot deduce return type from initializer list
}

Well, the difference is subtle – only braced lists of elements differ in deduction rules, but it’s worth remembering it.  Sometimes auto with its template type deduction rules is not appropriate as it removes among others references. Suppose you have code as below:

template<typename C>
auto get_value(C& vec, int index) {
  return vec[index];
}

std::vector<int> vec = {1,2,3};
get_value(vec, 1) = 4; //error: expression is not assignable

Why the error? Because auto uses template type deduction, which strips reference in this case. This reference comes from the return type of the int& std::vector::operator[]( size_type pos ); which is used in this call: vec[index], and which returns int&.  To preserve reference, use decltype(auto) instead of auto. The use of decltype(auto) is new C++14 syntax:

template<typename C>
decltype(auto) get_value(C& vec, int index) {
  return vec[index];
}

std::vector<int> vec = {1,2,3};
get_value(vec, 1) = 4; // now it works

decltype rules for type inference are pretty simple; they do not strip any adornments and work in the above case perfectly. You may say that auto&would work as well, but there would be problems with containers returning proxy classes in T::operator[], like std::vector<bool>.

It’s important to understand that functions with auto return types are very similar to template functions. Their definitions must also be available in the place of use, and they also can be forward declared.

It’s perfectly fine to use auto return type deduction in function templates, for example:

template <class T> auto foo(T t);

You may also use it in recursive functions, as long as the return type is deduced before the recursive call is made:

// wrong
auto recurse_funct1(int n) {
  if (n > 0)
    return recures_funct(n-1); //error: function 'recurse_funct' with deduced return type cannot be used before it is defined
  return 0;
}
// correct
auto recurse_funct2(int n) {
  if (n == 0)
    return 0;
  if (n >= 1)
    return recurse_funct2(n-1);
  return 0;
}

Finally, it’s not allowed to use auto return type deduction in virtual functions.

4. Conclusions

Function auto return type deduction is long awaited addition to C++. It was present in C++11 lambdas where you didn’t have to specify return type : auto fn = []() { return 123; } here return type is int, but for some reasons regular functions could not use this feature. Now you can write auto foo() { return 123; } and benefit from more abstracted form of programming.

references:
[1] http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3638.html

Leave a Reply

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