skip to content
Mehdi Mehdikhani
Table of Contents

decltype is C++11’s way of getting the type of an expression at compile time. It’s more precise than auto and essential for advanced template programming.

What decltype Actually Does

decltype deduces the exact type of an expression, including references and const qualifiers:

int x = 42;
const int& y = x;
decltype(x) a; // int
decltype(y) b = x; // const int&
decltype(42) c; // int

Unlike auto, decltype preserves everything about the type.

The Difference from auto

auto strips away references and const, but decltype keeps them:

const int& get_value();
auto x = get_value(); // int (strips const and reference)
decltype(get_value()) y = x; // const int& (preserves everything)

This precision makes decltype perfect for templates where you need exact type matching.

When I Actually Use decltype

Most of the time, I use decltype in these situations:

  1. Template return types: When the return type depends on the arguments:
template<typename T, typename U>
auto multiply(T a, U b) -> decltype(a * b) {
return a * b;
}
// The return type is exactly what a * b produces
auto result = multiply(3, 2.5); // double
  1. Perfect forwarding wrappers: Preserving exact types:
template<typename F, typename... Args>
auto wrapper(F&& func, Args&&... args) -> decltype(func(args...)) {
// Do some setup
return func(std::forward<Args>(args)...);
}
  1. SFINAE and type traits: Checking if expressions are valid:
template<typename T>
auto has_size_method(T&& t) -> decltype(t.size(), std::true_type{});
std::false_type has_size_method(...);
  1. Variable declarations: When you need the exact type of a complex expression:
std::map<std::string, std::vector<int>> complex_map;
decltype(complex_map.begin()->second) vector_ref = some_vector;

decltype with Expressions

decltype behaves differently with expressions vs identifiers:

int x = 42;
decltype(x) // int (identifier)
decltype((x)) // int& (expression in parentheses)

The parentheses matter. decltype((x)) treats x as an expression, which is an lvalue, so it becomes a reference.

Auto vs decltype(auto)

C++14 added decltype(auto) which combines both:

const int& get_ref();
auto x = get_ref(); // int
decltype(auto) y = get_ref(); // const int&

decltype(auto) uses decltype rules for deduction instead of auto rules.

Real World Example

Here’s how I use it for a generic cache:

template<typename Key, typename Function>
class MemoCache {
using ReturnType = decltype(std::declval<Function>()(std::declval<Key>()));
std::unordered_map<Key, ReturnType> cache_;
Function func_;
public:
MemoCache(Function f) : func_(f) {}
auto get(const Key& key) -> decltype(func_(key)) {
auto it = cache_.find(key);
if (it != cache_.end()) {
return it->second;
}
auto result = func_(key);
cache_[key] = result;
return result;
}
};
// Usage
auto cache = MemoCache([](int x) { return x * x; });
auto result = cache.get(5); // Returns int

Template Metaprogramming

decltype is essential for checking if types have certain members:

template<typename T>
struct has_begin {
private:
template<typename U>
static auto test(U* u) -> decltype(u->begin(), std::true_type{});
static std::false_type test(...);
public:
static constexpr bool value = decltype(test(static_cast<T*>(nullptr)))::value;
};
static_assert(has_begin<std::vector<int>>::value, "vector should have begin()");

The Pattern I Follow

Use decltype when you need exact type preservation:

// For return type deduction
template<typename T, typename U>
auto combine(T&& t, U&& u) -> decltype(t + u) {
return std::forward<T>(t) + std::forward<U>(u);
}
// For variable declarations with complex types
decltype(some_complex_expression()) result = compute_something();
// For template parameter deduction
template<typename Container>
void process(Container&& c) {
using ValueType = decltype(*std::begin(c));
// Work with ValueType
}

C++14 Simplification

C++14 made many decltype uses unnecessary with auto return type deduction:

// C++11 - needed decltype
template<typename T, typename U>
auto multiply(T a, U b) -> decltype(a * b) {
return a * b;
}
// C++14 - auto deduction works
template<typename T, typename U>
auto multiply(T a, U b) {
return a * b;
}

But decltype is still essential when you need precise control over type deduction or for template metaprogramming.