auto & decltype
auto and decltype are two type-deduction tools
introduced (and expanded) in modern C++. Both let the compiler figure out a
type for you instead of writing it out by hand, which becomes especially
valuable with long template and iterator type names.
auto — Deduce from the Initializer
auto tells the compiler: "look at the value on the right-hand
side and figure out this variable's type from it." It requires an
initializer — there is nothing to deduce from otherwise.
CPP
auto count = 10; // int
auto price = 19.99; // double
auto name = std::string{"Ada"}; // std::string
auto ptr = &count; // int*auto shines when the real type is long and noisy — most
famously with STL iterators:
CPP
#include <vector>
#include <map>
int main() {
std::map<std::string, std::vector<int>> data;
// Without auto:
std::map<std::string, std::vector<int>>::iterator it1 = data.begin();
// With auto — identical behavior, far more readable:
auto it2 = data.begin();
return 0;
}auto in Range-Based for Loops
CPP
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers{1, 2, 3, 4, 5};
for (auto n : numbers) { // n deduced as int (copy)
std::cout << n << " ";
}
for (const auto& n : numbers) { // n deduced as const int& (no copy, read-only)
std::cout << n << " ";
}
return 0;
}auto can hide important type information
Overusing
auto can make code harder to review — a reader may not be able to tell at a glance whether a variable is a value, a reference, a pointer, or an expensive-to-copy object. It can also silently deduce a type you did not intend, such as deducing int when you meant double because the initializer had no decimal point. Use auto when the type is obvious from context (like iterators, or immediately to the right of a constructor call) and prefer writing the type explicitly when clarity matters more than brevity.decltype — Deduce Without Evaluating
decltype(expression) yields the type of an expression without
actually evaluating it. Unlike auto, it does not require an
initializer and it preserves references and const-ness exactly, which makes
it valuable in generic/template code.
CPP
#include <iostream>
int main() {
int x = 5;
int& refX = x;
decltype(x) a = 10; // int
decltype(refX) b = x; // int& — decltype preserves the reference
std::cout << a << " " << b << std::endl;
return 0;
}decltype in a Template Return Type
A classic use case is a function template whose return type depends on the types of its parameters — for example, adding two values of potentially different types.
CPP
#include <iostream>
// Trailing return type: the return type is deduced from a + b,
// which might be int, double, or some other type depending on T and U.
template <typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {
return a + b;
}
int main() {
std::cout << add(2, 3.5) << std::endl; // double, 5.5
std::cout << add(1, 2) << std::endl; // int, 3
return 0;
}autodeduces from a value you already have in hand (the initializer)decltypededuces from an expression's declared type, without evaluating itSince C++14,
decltype(auto)combines both: deduce likeautobut preserve references/const likedecltype
Note
Since C++14, function return types can also use plain
auto and let the compiler deduce the return type from the return statement, which avoids the need for a trailing decltype in many simpler cases.