decltype in C++
/ 3 min read
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; // intdecltype(y) b = x; // const int&decltype(42) c; // intUnlike 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:
- 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 producesauto result = multiply(3, 2.5); // double- 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)...);}- 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(...);- 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(); // intdecltype(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; }};
// Usageauto cache = MemoCache([](int x) { return x * x; });auto result = cache.get(5); // Returns intTemplate 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 deductiontemplate<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 typesdecltype(some_complex_expression()) result = compute_something();
// For template parameter deductiontemplate<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 decltypetemplate<typename T, typename U>auto multiply(T a, U b) -> decltype(a * b) { return a * b;}
// C++14 - auto deduction workstemplate<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.