Без ясного розуміння виведення типів ефективне програмування сучасним 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 — посилання або вказівник (не універсальне)

  1. Якщо expr — посилання, частина з посиланням відкидається.
  2. 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 не «чистить» тип: повертає точний тип сутності / виразу.

  1. Просте ім’я (id-expression) — оголошений тип як є.
  2. Складніший lvalue-вираз — Type&.
  3. Складніший 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.