Return Type Deduction (C++14)

Automatic return type deduction for functions and lambdas

The Evolution of Return Types

C++98/03

Explicit return types only

C++11

Trailing return types (decltype)

C++14

Automatic return type deduction

Before C++14: Explicit Types

// C++98/03: Must specify return type explicitly
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }

// C++11: Trailing return type for complex cases
template<typename T, typename U>
auto multiply(T a, U b) -> decltype(a * b) {
    return a * b;
}

C++14: Auto Return Type

C++14 allows auto as the return type, with the compiler deducing the actual type from the return statements.

// C++14: Automatic return type deduction
template<typename T, typename U>
auto add(T a, U b) {
    return a + b;  // Return type is decltype(a + b)
}

// Regular functions too
auto get_value() {
    return 42;  // Returns int
}

auto get_pi() {
    return 3.14159;  // Returns double
}

Multiple Return Statements

When multiple return statements exist, they must all deduce to the same type:

// Valid: All returns deduce to int
auto get_number(bool flag) {
    if (flag) {
        return 1;  // int
    }
    return 0;      // int (same type)
}

// ERROR: Mixed return types
auto bad_function(bool flag) {
    if (flag) {
        return 1;      // int
    }
    return 3.14;       // double - ERROR!
}

Return Type Deduction Rules

  • autoReturns by value (removes cv-qualifiers and references)
  • auto&Returns lvalue reference (preserves reference)
  • const auto&Returns const lvalue reference
  • decltype(auto)Preserves cv-qualifiers and references exactly (C++14)

decltype(auto) - Preserving Type Information

Use decltype(auto) when you need to preserve references and cv-qualifiers:

template<typename Container>
auto get_first(Container& c) -> decltype(auto) {
    return c.front();  // Preserves reference type
}

std::vector<int> vec = {1, 2, 3};
get_first(vec) = 100;  // Works! Returns int&

// With plain auto, this wouldn't work:
template<typename Container>
auto get_first_auto(Container& c) {
    return c.front();  // Returns int (copy), not int&
}

Recursive Functions

Return type deduction doesn't work with recursive functions without a forward declaration:

// ERROR: Cannot deduce return type for recursion
auto factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);  // Error: factorial not yet defined
}

// SOLUTION 1: Forward declaration with trailing return
template<typename T>
auto factorial(T n) -> T {
    if (n <= 1) return T{1};
    return n * factorial(n - 1);
}

// SOLUTION 2: C++23 deducing this (see later chapter)
auto factorial = [](this auto&& self, int n) -> int {
    if (n <= 1) return 1;
    return n * self(n - 1);
};

Best Practices

  • Use auto return type for simple, obvious cases
  • Use decltype(auto) when forwarding returns
  • Prefer explicit return types for public API functions (clearer interface)
  • Don't use auto return when the type is not obvious from context
  • Avoid mixing different return types in the same function