3.1.8 decltype(auto)

What was the problem

We saw in [3.1.6] that we can now omit the return type of a function. So can you tell why the assert won’t pass in the code below?

#include <type_traits> 
#include <utility> 
 
template<typename F, typename... Args> 
auto invoke(F&& f, Args&&... args) 
{ 
  return f(std::forward<Args>(args)...); 
} 
 
int& at(int* v, int i) 
{ 
  return v[i]; 
} 
 
// Note that no call occurs here since decltype(expression) produces 
// the the type at compile-time, _as-if_ the expression was 
// evaluated. So it is safe to use nullptr in the arguments. 
static_assert 
  (std::is_same 
   < 
     decltype(invoke(&at, nullptr, 42)), 
     decltype(at(nullptr, 42)) 
   >::value, 
   "");

If you ever encounter one of the few use cases where auto it is actually useful, you may be surprised that the deduced type does not match your expectation. In the above code one could read the return type of invoke as “whatever is returned by f”. Unfortunately, auto does not work like that.

The auto keyword does not take into account the reference nor the constness of the expression. That means that the type of auto_value in the code below is actually int, not const int& like reference.

#include <type_traits> 
 
int i; 
const int& reference = i; 
auto auto_value = reference; 
static_assert(std::is_same<int, decltype(auto_value)>::value, "");
How the Problem is Solved

Most of the time, auto can be replaced by a more explicit type, but in the case of the invoke function the explicit version requires to repeat the whole body (see [3.1.6]). As a workaround, C++14 introduces the decltype(auto) specifier, which keeps all the properties of the type of the statement from which the type is deduced:

#include <type_traits> 
#include <utility> 
 
template<typename F, typename... Args> 
decltype(auto) invoke(F&& f, Args&&... args) 
{ 
  return f(std::forward<Args>(args)...); 
} 
 
int& at(int* v, int i) 
{ 
  return v[i]; 
} 
 
// It works! 
static_assert 
  (std::is_same 
   < 
     decltype(invoke(&at, nullptr, 42)), 
     decltype(at(nullptr, 42)) 
   >::value, 
   "");

As usual, decltype(auto) can be used anywhere a type is required, as long as there is a way for the compiler to deduce the type.

As usual, there is no good reason to prefer that when a more explicit type can be used.