Без ясного розуміння виведення типів ефективне програмування сучасним C++ майже неможливе: шаблони функцій, auto, decltype, decltype(auto) — і ще більше контекстів у C++17–C++23. Фундаментальні правила не скасовані; стандарти їх розширили (CTAD, abbreviated function templates, concepts, NTTP з auto, deducing this).
Усе тримається на трьох стовпах.
1. Шаблони функцій
template <typename T>
void f(ParamType param);
f(expr); // компілятор виводить T і ParamType окремо
T і ParamType — різні речі. Якщо ParamType — const T&, а аргумент — int, то T = int, а ParamType = const int&.
Сценарій A: ParamType — посилання або вказівник (не універсальне)
- Якщо
expr— посилання, частина з посиланням відкидається. const/volatileаргументу зберігаються і стають частиноюT.
template <typename T> void f_ref(T& param);
template <typename T> void f_cref(const T& param);
int x = 27;
const int cx = x;
const int& rx = x;
// f_ref(T& param):
f_ref(x); // T = int, param = int&
f_ref(cx); // T = const int, param = const int&
f_ref(rx); // T = const int, param = const int&
// f_ref(27); // помилка: non-const lvalue reference не прив'язується до rvalue
// f_cref(const T& param):
f_cref(x); // T = int, param = const int&
f_cref(cx); // T = int, param = const int& (const уже в ParamType)
f_cref(rx); // T = int, param = const int&
f_cref(27); // T = int, param = const int& (const& може прив'язатися до rvalue)
Хоча шаблон оголошено як T& без явного const, передача cx валідна: компілятор виводить T як const int.
Сценарій B: універсальне (forwarding) посилання T&&
Універсальним воно є лише коли T виводиться саме тут (T&&, а не std::vector<T>&& чи const T&&).
- lvalue:
Tвиводиться як lvalue-посилання (T = Type&), далі reference collapsing →paramстаєType&. - rvalue: звичайні правила —
T— не-посилання,param—Type&&.
Згортання посилань: &+& → &; &+&& → &; &&+& → &; &&+&& → &&.
template <typename T> void f_fwd(T&& param);
int x = 27;
const int cx = x;
f_fwd(x); // lvalue → T = int&, param = int&
f_fwd(cx); // lvalue → T = const int&, param = const int&
f_fwd(27); // rvalue → T = int, param = int&&
Це єдиний випадок у шаблонах, де сам T може бути виведений як посилання.
Сценарій C: передача за значенням (T)
Копія: посилання відкидаються; top-level const / volatile теж (копію можна змінювати незалежно від оригіналу).
template <typename T> void f_val(T param);
int x = 27;
const int cx = x;
const int& rx = x;
const char* const ptr = "Hello";
f_val(x); // T = int
f_val(cx); // T = int (const відкинуто)
f_val(rx); // T = int (посилання і const відкинуто)
f_val(ptr); // T = const char*
// top-level const покажчика зник, const даних за адресою лишився
Масиви і функції
За значенням (T) — decay у покажчик. За посиланням (T&) — повний тип зберігається.
const char name[] = "C++"; // const char[4]
template <typename T> void f1(T param);
template <typename T> void f2(T& param);
f1(name); // T = const char*, param = const char*
f2(name); // T = const char[4], param = const char(&)[4]
Тому можна писати, наприклад:
template <typename T, std::size_t N>
constexpr std::size_t array_size(T (&)[N]) noexcept {
return N;
}
Функція за посиланням дає посилання на функцію (void(&)()), а не покажчик.
2. auto
auto ≈ виведення шаблонів: auto як T, модифікатори (const auto&, auto&&) як ParamType.
int x = 10;
const int cx = x;
const int& rx = x;
auto a = x; // int (копія)
auto b = cx; // int (const відкинуто)
auto c = rx; // int (посилання і const відкинуто)
const auto& d = x; // const int&
auto& e = cx; // const int& (const увійшов у тип)
auto&& f1 = x; // lvalue → int&
auto&& f2 = 10; // rvalue → int&&
Виняток: braced initializer
auto x1 = 27; // int
auto x2 = {27}; // std::initializer_list<int>
template <typename T> void f(T param);
// f({27}); // помилка: тип зі списку ініціалізації не виводиться
3. decltype і decltype(auto)
decltype не «чистить» тип: повертає точний тип сутності / виразу.
- Просте ім’я (
id-expression) — оголошений тип як є. - Складніший lvalue-вираз —
Type&. - Складніший rvalue-вираз —
Type.
int x = 0;
decltype(x) a = x; // int
decltype((x)) b = x; // int& ((x) — lvalue-вираз)
decltype(auto) (C++14): вивід за правилами decltype, а не шаблонів — зберігає посилання і const.
std::string& get_name();
auto name1 = get_name(); // std::string — копія
decltype(auto) name = get_name(); // std::string&
Увага на зайві дужки у return:
decltype(auto) ok() {
int x = 42;
return x; // int
}
decltype(auto) buggy() {
int x = 42;
return (x); // int& на локальну — dangling
}
Шпаргалка
| Конструкція | const |
& |
Коли зручно |
|---|---|---|---|
auto x = expr; |
відкидається | відкидається | незалежна копія |
const auto& x = expr; |
завжди є | завжди є | читання без копії (lvalue і rvalue) |
auto& x = expr; |
зберігається | завжди є | зміна оригінального lvalue |
auto&& x = expr; |
зберігається | згортається | forwarding / std::forward |
decltype(auto) x = expr; |
зберігається | точно як у виразі | прокидання типу повернення |
Джерело формулювань і прикладів — розбір виведення для T& і трьох стовпів (шаблони / auto / decltype) у стилі правил Effective Modern C++; нововведення C++17–C++23 лише розширюють контексти застосування, а не замінюють базові сценарії A–C.