Функции
Объявления, перегрузка, аргументы по умолчанию, inline, лямбды и callable-объекты.
37 вопросов
JuniorТеорияОчень частоПередача аргументов по значению, ссылке и указателю — когда что использовать?
Передача аргументов по значению, ссылке и указателю — когда что использовать?
По значению: дешёвые в копировании типы (два-три машинных слова) или когда нужна копия. По const T&: только читаемые и дорогие в копировании. По T&: меняем объект вызывающего. По T*: когда null значим.
Типичные ошибки
- ✗Передавать большие объекты по значению в производительно-критичном коде — вызывает ненужное копирование
- ✗Использовать не-const ссылку, когда значение только читается — вводит вызывающих в заблуждение, что объект может быть изменён
- ✗Разыменовывать указатель-параметр без проверки на null, когда указатель может быть null по контракту
Уточняющие вопросы
- →Что такое паттерн 'sink parameter' и когда передача по значению +
std::moveпревосходит перегрузку по lvalue/rvalue ссылкам? - →Чем семантически отличается передача по
T&&(rvalue-ссылка) от передачи поT?
JuniorДебаггингОчень частоЧто произойдёт, если вернуть ссылку на локальный объект?
Что произойдёт, если вернуть ссылку на локальный объект?
Неопределённое поведение: count уничтожена при возврате, и ссылка c стала висячей
Типичные ошибки
- ✗Ждать, что ссылка продлит жизнь
count: время жизни локальной переменной не продлевается послеreturn, и чтениеc— неопределённое поведение. - ✗Ждать ошибку компиляции: GCC и Clang по умолчанию, даже без
-Wall, только предупреждают, и программа собирается.
Уточняющие вопросы
- →Как пропуск копирования (RVO/NRVO) делает дешёвым возврат большого объекта по значению?
- →Безопасен ли возврат ссылки на
static-локальную переменную в многопоточной программе?
JuniorТеорияОчень частоЧто такое лямбда? Как захватывать внешние переменные?
Что такое лямбда? Как захватывать внешние переменные?
Анонимный функциональный объект inline. Захваты: [=] значение, [&] ссылка, [x]/[&x], [y = expr] init, [*this] копия. Без захватов конвертируется в указатель на функцию.
Типичные ошибки
- ✗Использовать захват
[&]в лямбде, хранящейся дольше захваченных переменных — висячие ссылки - ✗Забывать, что
[=]в функции-члене захватываетthisпо указателю — используйте[*this](C++17) для безопасной копии - ✗Ожидать, что два одинаковых выражения лямбды имеют одинаковый тип — у каждой лямбды уникальный тип
Уточняющие вопросы
- →Что такое обобщённая лямбда (C++14) и как она связана с шаблонами функций?
- →Когда вы использовали бы
mutable-лямбду?
JuniorТеорияОчень частоЧто такое перегрузка функций и как работает разрешение перегрузки?
Что такое перегрузка функций и как работает разрешение перегрузки?
Несколько функций имеют одно имя, но различаются типами или числом параметров. Компилятор выбирает: точное совпадение, продвижения, стандартные преобразования, пользовательские преобразования.
Типичные ошибки
- ✗Попытка перегрузить функцию только по возвращаемому типу — в C++ это недопустимо
- ✗Создание неоднозначных перегрузок, вынуждающих компилятор выдавать ошибку
- ✗Забывать, что аргументы по умолчанию могут создать неоднозначность с существующими перегрузками
Уточняющие вопросы
- →Как декорирование имён (name mangling) связано с перегрузкой?
- →Что происходит, если ни одна перегрузка не совпадает точно?
MiddleДебаггингОчень частоПочему эта возвращённая лямбда читает освобождённую память?
Почему эта возвращённая лямбда читает освобождённую память?
Лямбда захватывает count по ссылке ([&]), но count уничтожается, когда makeCounter возвращается, поэтому вызов возвращённого std::function читает висячую ссылку — UB. Решение: захват по значению, [count]() mutable { return ++count; }.
Типичные ошибки
- ✗Считать, что захват по ссылке продлевает время жизни захваченной переменной
- ✗Винить std::function вместо захвата по ссылке
- ✗Думать, что добавление mutable исправляет то, что на деле является ошибкой времени жизни
Уточняющие вопросы
- →Когда захват по ссылке в лямбде действительно безопасен?
- →Почему захвату по значению нужен
mutable, чтобы его можно было изменять?
JuniorТеорияЧастоКаковы правила применения аргументов по умолчанию и какие подводные камни они создают?
Каковы правила применения аргументов по умолчанию и какие подводные камни они создают?
Аргументы по умолчанию задаются справа налево (последние первые). Разрешаются в точке вызова по видимому там объявлению, а не по определению.
Типичные ошибки
- ✗Задавать одной функции разные значения по умолчанию в разных TU — с C++20 это нарушение ODR: программа некорректна, диагностика не требуется, и GCC с Clang даже с LTO молча её принимают
- ✗Комбинирование аргументов по умолчанию с перегрузками, создающее неоднозначные вызовы
- ✗Ожидание, что виртуальные переопределения наследуют аргументы по умолчанию от базового класса
Уточняющие вопросы
- →Почему виртуальные функции и аргументы по умолчанию плохо сочетаются?
- →Что произойдёт, если две единицы трансляции видят разные значения по умолчанию для одной функции?
JuniorТеорияЧастоКаков порядок вычисления аргументов функции в C++?
Каков порядок вычисления аргументов функции в C++?
Порядок не специфицирован и различается между компиляторами. С C++17 каждый аргумент вычисляется целиком до начала другого, поэтому f(a++, a++) — UB до C++17 — просто передаёт a и a+1 в каком-то порядке.
Типичные ошибки
- ✗Считать порядок слева направо, раз так делает один компилятор, — GCC и Clang на x86-64 уже расходятся
- ✗Полагаться на то, что передаст
f(i++, i), — с C++17 это не UB, но прочитается лиiдо или после инкремента — не специфицировано - ✗Думать, что C++17 зафиксировал порядок аргументов, — он лишь упорядочил вычисление вызываемой функции перед аргументами и запретил аргументам перемежаться
Уточняющие вопросы
- →Какие правила упорядочения ввёл C++17, которых не было в C++14?
- →Как переписать
f(i++, i++), чтобы передаваемые значения не зависели от компилятора?
JuniorТеорияЧастоЧто выведет программа?
Что выведет программа?
1 5
Открыть задачу →Типичные ошибки
- ✗Ждать
5 5:[x]копируетxв момент создания лямбды, и последующие измененияxдо неё не доходят. - ✗Ждать
1 1:[x]копируетxв лямбду, но самаxостаётся обычной переменной, иx = 5её меняет.
Уточняющие вопросы
- →Что выведет программа, если заменить захват
[x]на[&x]? - →Почему
[x]() { ++x; }не компилируется безmutable?
JuniorТеорияЧастоКакой код возврата будет у этой программы?
Какой код возврата будет у этой программы?
0
Открыть задачу →Типичные ошибки
- ✗Ждать случайное значение: в любой другой функции, возвращающей значение, выход без
return— неопределённое поведение, ноmainвозвращает0. - ✗Ждать ошибку компиляции: из функций, которые возвращают значение, только
mainможет закончиться безreturn.
Уточняющие вопросы
- →Что произойдёт, если другая функция, возвращающая
int, дойдёт до закрывающей скобки безreturn? - →Что означают макросы
EXIT_SUCCESSиEXIT_FAILUREиз<cstdlib>как коды возврата?
JuniorТеорияЧастоЧто выведет программа?
Что выведет программа?
double
Открыть задачу →Типичные ошибки
- ✗Ждать
int:float→int— преобразование,float→double— продвижение, а продвижение лучше преобразования. - ✗Ждать ошибку неоднозначности: она бывает, только когда лучшие перегрузки одинаково хороши, а здесь только к
doubleведёт продвижение.
Уточняющие вопросы
- →Что вызовет
print(2.5f), если объявлены толькоprint(int)иprint(long)? - →Почему
print('a')здесь вызываетprint(int), а неprint(long)?
JuniorТеорияЧастоЧто выведет программа?
Что выведет программа?
1 10
Открыть задачу →Типичные ошибки
- ✗Ждать
10 10:x— копияn, поэтому присваивание переменнойxне меняетn. - ✗Ждать
1 1: функция может менять свою копию, иreturn xвернёт уже изменённое значение.
Уточняющие вопросы
- →Как изменить
reset, чтобы вызов присвоил10и самойn? - →Когда передать параметр по
const-ссылке лучше, чем по значению?
JuniorТеорияЧастоЧто такое указатель на функцию и как его объявить?
Что такое указатель на функцию и как его объявить?
Хранит адрес функции с конкретной сигнатурой. Объявление: int (*fp)(double, double);. Вызов: fp(a, b). Для читаемости — using BinaryOp = int(*)(double, double);.
Типичные ошибки
- ✗Путать указатель на функцию с указателем на функцию-член — указатели на функции-члены имеют другой, несовместимый тип
- ✗Не использовать
typedef/usingдля типа указателя на функцию — синтаксис сырого объявления нотационно трудночитаем - ✗Хранить захватывающую лямбду в сыром указателе на функцию — только не-захватывающие лямбды неявно конвертируются
Уточняющие вопросы
- →В чём разница между
std::functionи сырым указателем на функцию с точки зрения накладных расходов? - →Как хранить указатель на не-статическую функцию-член и вызывать его позже?
JuniorТеорияЧастоЧто такое ленивые вычисления (short-circuit evaluation) в C++?
Что такое ленивые вычисления (short-circuit evaluation) в C++?
&& и || вычисляют левый операнд первым; если результат определён — правый пропускается. Гарантировано стандартом и упорядочено. Тернарный ? : тоже ленив. Перегруженные &&/|| — НЕТ.
Типичные ошибки
- ✗Полагаться на ленивое вычисление с перегруженными
&&/||— перегруженные операторы всегда вычисляют обе стороны - ✗Размещать ресурсоёмкое по побочным эффектам выражение справа, ожидая, что оно всегда выполнится — не выполнится, если левая часть определяет результат
- ✗Не знать, что оператор запятая тоже упорядочивает слева направо, но
|и&не вычисляются лениво
Уточняющие вопросы
- →Чем
std::logical_andиstd::logical_orотличаются от&&и||? - →Когда ленивое вычисление полезно для защиты от разыменования нулевого указателя?
JuniorТеорияЧастоЧто такое стековый кадр функции и что может вызвать stack overflow?
Что такое стековый кадр функции и что может вызвать stack overflow?
Область конкретного вызова: локальные переменные, сохранённые регистры, адрес возврата, аргументы. Stack overflow — превышение лимита стека вложенными вызовами или большими локальными.
Типичные ошибки
- ✗Считать, что стек растёт без практических ограничений
- ✗Выделять многомегабайтные массивы как локальные переменные
- ✗Путать стек вызовов с контейнером std::stack
Уточняющие вопросы
- →Где хранятся машинные инструкции функций?
- →Как избежать stack overflow в рекурсивном коде?
MiddleТеорияЧастоЧто такое argument-dependent lookup (ADL), и когда он удивляет?
Что такое argument-dependent lookup (ADL), и когда он удивляет?
ADL добавляет namespace типов аргументов вызова к множеству поиска неквалифицированного имени функции. Благодаря этому swap(a, b) или operator<< находят нужную перегрузку без квалификатора. Удивляет тем, что молча подтягивает непреднамеренную перегрузку из namespace аргумента.
Типичные ошибки
- ✗Писать
std::swap(a, b)вместо идиомыusing std::swap; swap(a, b), дающейADLнайти кастомныйswap - ✗Считать, что квалифицированный вызов
ns::f(x)всё ещё делаетADL— квалификация его полностью отключает - ✗Класть свободную функцию в
namespace std, чтобы её нашёлADL, вместо размещения рядом с типом
Уточняющие вопросы
- →Почему рекомендуется двухшаговая идиома
using std::swap; swap(a, b)? - →Как hidden friends взаимодействуют с ADL и обычным поиском?
MiddleТеорияЧастоКак работает C++14 return-type deduction и когда он не работает?
Как работает C++14 return-type deduction и когда он не работает?
auto f() { return expr; } выводит тип возврата по тем же правилам, что auto x = expr. Все return должны давать один тип. Рекурсия только после первого return.
Типичные ошибки
- ✗Два
returnс разными типами — ошибка компиляции - ✗Рекурсивный вызов до первого
return— компилятор ещё не вывел тип - ✗Forward-declare
auto f();и определение в другом файле — вызывающий не знает тип
Уточняющие вопросы
- →Почему
decltype(auto)важен для perfect-forwarding возврата? - →Чем
auto-возврат лямбды отличается от функции?
MiddleТеорияЧастоЧто значит constexpr у функции и как это эволюционировало в C++14/17/20?
Что значит constexpr у функции и как это эволюционировало в C++14/17/20?
Разрешает вычисление в compile-time при константных аргументах; иначе — в рантайме. C++11 — один return. C++14 — циклы, локальные. C++20 — virtual, аллокации, std::vector/std::string.
Типичные ошибки
- ✗Помечать всё
constexprбез нужды — не вредит, но раздувает API - ✗Вызывать не-constexpr функцию внутри constexpr — молча превращается в рантайм-вызов
- ✗Путать
constexprиconst—constexprдля объектов влечёт const, но у функций речь о вычислимости
Уточняющие вопросы
- →Чем
constexprотличается отconsteval? - →Что такое
std::is_constant_evaluated()и когда его использовать?
MiddleДебаггингЧастоКакие подводные камни у дефолтных аргументов в виртуальных функциях и между TU?
Какие подводные камни у дефолтных аргументов в виртуальных функциях и между TU?
Дефолты привязываются статически по типу указателя/ссылки. Base* b = new Derived; b->f(); берёт дефолт Base, хотя выполняется Derived::f; дефолт = 2 в переопределении мёртв.
Типичные ошибки
- ✗Переопределить virtual с другим дефолтом — вызов берёт дефолт базового класса
- ✗Разные дефолты в двух заголовках — нарушение ODR
- ✗Дефолты, зависящие от глобалов — неочевидное поведение в точке вызова
Уточняющие вопросы
- →Почему стандарт выбрал статическое связывание для дефолтов?
- →Как
std::optional/ sentinel чисто заменяют дефолтные аргументы?
MiddleТеорияЧастоЧто такое функтор? Напишите пример.
Что такое функтор? Напишите пример.
Функтор (функциональный объект) — любой класс, перегружающий operator(). В отличие от указателя на функцию, хранит состояние в членах и инлайнится — без накладных расходов на стирание типа.
Типичные ошибки
- ✗Забывать помечать
operator()какconst, если функтор не изменяет состояние — предотвращает использование сconst-алгоритмами - ✗Хранить дорогостоящее для копирования состояние в функторе, передаваемом по значению в алгоритм — STL может внутренне копировать функтор
- ✗Использовать
std::function, когда параметр шаблона принимает callable —std::functionдобавляет ненужные накладные расходы на удаление типа
Уточняющие вопросы
- →В чём разница между состоятельным функтором и лямбдой с захватами?
- →Как
std::bindсоотносится с функторами и почему лямбды в основном вытеснили его?
MiddleТеорияЧастоЧто на самом деле гарантирует ключевое слово inline в современном C++?
Что на самом деле гарантирует ключевое слово inline в современном C++?
inline снимает ограничение ODR — одно определение может быть в нескольких TU (нужно для функций в заголовках). Подсказка о встраивании в точку вызова современными компиляторами игнорируется.
Типичные ошибки
- ✗Предположение, что inline всегда устраняет накладные расходы на вызов
- ✗Мнение, что inline придаёт функции внутреннее связывание
- ✗Размещение не-inline определений в заголовках в ожидании отсутствия ошибок компоновщика
Уточняющие вопросы
- →В чём разница между inline и __forceinline / __attribute__((always_inline))?
- →Когда стоит использовать NOINLINE?
MiddleТеорияЧастоЧто означает mutable у лямбды и чем опасен захват this?
Что означает mutable у лямбды и чем опасен захват this?
По умолчанию operator() у лямбды const — захваченные по значению менять нельзя. mutable снимает const. Захват this хранит указатель — висячий, если объект умер раньше.
Типичные ошибки
- ✗Думать, что mutable меняет исходную переменную, захваченную по значению
- ✗Захватывать this в async-callback без контроля времени жизни объекта
- ✗Использовать [&] в callback, который живёт дольше текущей области
Уточняющие вопросы
- →Как shared_ptr или weak_ptr помогают в async-callback?
- →В чём разница между [this] и [*this]?
MiddleТеорияЧастоЧто такое name mangling в C++ и как он работает с extern "C"?
Что такое name mangling в C++ и как он работает с extern "C"?
Mangling кодирует сигнатуру (параметры, namespace, шаблоны) в имя символа, чтобы линкер различал перегрузки. У каждого ABI своя схема. extern "C" отключает mangling для C-interop.
Типичные ошибки
- ✗Забыть
extern "C"у функции, вызываемой из C — link error - ✗Пытаться перегрузить
extern "C"— mangling нет, поэтому нельзя - ✗Несовместимые ABI при линковке объектников разных компиляторов
Уточняющие вопросы
- →Как читать mangled-имена (
c++filt)? - →Почему на Linux Itanium ABI, несмотря на название?
MiddleПроизводительностьЧастоКакова рантайм-стоимость виртуального вызова и когда это важно?
Какова рантайм-стоимость виртуального вызова и когда это важно?
Две indirection: загрузка vptr, загрузка указателя на функцию из vtable, прыжок. Стоимость — несколько ns плюс возможный cache miss; важнее то, что вызов не инлайнится.
Типичные ошибки
- ✗Профилировать без
-O2и делать вывод о медленности virtual - ✗Добавлять
virtualк листовым методам, которым подходитfinal(теряется devirt) - ✗Выбирать CRTP для мелкого полиморфизма с парой типов — overkill
Уточняющие вопросы
- →Как компилятор делает devirtualisation?
- →Почему
finalпомогает даже у класса (не только у метода)?
SeniorТеорияЧастоЧто такое perfect forwarding и как его реализовать?
Что такое perfect forwarding и как его реализовать?
Сохраняет value category (lvalue/rvalue) и cv-квалификацию при передаче. Шаблон: template<class... A> void wrap(A&&... a) { f(std::forward<A>(a)...); }. Без forward rvalue становятся lvalue.
Типичные ошибки
- ✗Использовать
std::move(args)...вместоstd::forward<Args>(args)...— ошибочно перемещает lvalue - ✗Путать
T&&в не-шаблоне с forwarding-ссылкой — там это просто rvalue ref - ✗Перегружать forwarding-ref и конкретные типы — forwarding-ref обычно побеждает и перехватывает вызовы
Уточняющие вопросы
- →Что такое reference collapsing?
- →Чем C++20
std::forward_likeотличается отstd::forward?
JuniorТеорияИногдаЧто выведет программа?
Что выведет программа?
A
Открыть задачу →Типичные ошибки
- ✗Ждать
AB:returnсразу завершаетmain, и следующая инструкция не выполняется. - ✗Ждать ошибку компиляции: код после
returnкомпилируется, но никогда не выполняется.
Уточняющие вопросы
- →Почему компилятор принимает строку с выводом
B, хотя она никогда не выполнится? - →Появится ли вывод в
std::coutбез перевода строки, когдаmainвернёт управление?
MiddleТеорияИногдаЧто такое контракт функции (предусловия, постусловия, инварианты)?
Что такое контракт функции (предусловия, постусловия, инварианты)?
Обязательства между вызывающим и вызываемым. Предусловия — выполняет вызывающий. Постусловия — гарантирует функция. Инварианты — свойства класса после публичных операций.
Типичные ошибки
- ✗Молча обрабатывать нарушенные предусловия вместо assert — скрывает баги вызывающего и приводит к более сложным для отладки сбоям далее
- ✗Не документировать контракт — вызывающие не могут знать, что допустимо, без документации
- ✗Проверять предусловия в release-сборках, когда они дорогостоящие — используйте
assert, который убирается при NDEBUG
Уточняющие вопросы
- →Чем
assert,[[expects]]и бросание исключений отличаются как механизмы проверки предусловий? - →Что такое Design by Contract (DbC) и какие языки поддерживают его нативно?
MiddleТеорияИногдаЧто такое обобщённая лямбда, и как она связана с шаблоном функции?
Что такое обобщённая лямбда, и как она связана с шаблоном функции?
Обобщённая лямбда (C++14) использует auto для параметра, делая её operator() шаблоном-членом. Каждый различный тип аргумента инстанцирует отдельный operator(), точно как шаблон функции. Замыкание — один тип; шаблонным является лишь оператор вызова, который подстраивается под точку вызова.
Типичные ошибки
- ✗Думать, что каждый вызов с
auto-параметром создаёт новый тип замыкания — тип один, шаблонным являетсяoperator() - ✗Ожидать, что два типа аргументов разделят одно тело
operator()— каждый тип инстанцирует своё, со своими ошибками - ✗Забывать
decltype(auto)илиauto&&для параметров, которые должны идеально пробрасываться внутри тела
Уточняющие вопросы
- →Как добавить лямбде явный список параметров шаблона (C++20)?
- →Когда обобщённая лямбда предпочтительнее именованного шаблона функции?
MiddleТеорияИногдаЧто такое init-capture, и зачем он нужен для захвата переменной перемещением?
Что такое init-capture, и зачем он нужен для захвата переменной перемещением?
Init-capture (C++14) вводит новый член замыкания с инициализатором: [p = std::move(ptr)]. Обычный [ptr] только копирует; синтаксис захвата не умеет выразить перемещение. Init-capture позволяет инициализировать член любым выражением — переместить в него unique_ptr либо вычислить значение.
Типичные ошибки
- ✗Пытаться захватить
unique_ptrчерез обычный[ptr]и получить ошибку об удалённом конструкторе копирования - ✗Забывать, что член init-capture инициализируется один раз при создании лямбды, а не на каждом вызове
- ✗Называть член init-capture так же, как исходную переменную, и путать, какую из них использует тело
Уточняющие вопросы
- →Как переместить член класса в лямбду внутри функции-члена?
- →Может ли init-capture использовать pack expansion в C++20?
MiddleКодИногдаРеализуйте минимальный механизм signal/slot с использованием лямбд и std::function
Реализуйте минимальный механизм signal/slot с использованием лямбд и std::function
Храните колбэки в std::vector<std::function<void()>> с token-id для disconnect, и пробегайте на emit(). Лямбды с захватами подходят — std::function стирает тип замыкания.
Типичные ошибки
- ✗Захват локальных переменных по ссылке, когда лямбда живёт дольше области видимости
- ✗Копирование
std::functionс большими захватами вместо предпочтительногоstd::move - ✗Игнорирование того, что
std::functionобычно даёт около 10-20% накладных расходов относительно прямого вызова из-за выделения памяти и стирания типа
Уточняющие вопросы
- →Как сделать подключение слотов потокобезопасным?
- →Что такое
std::move_only_functionи когда его стоит предпочестьstd::function?
MiddleТеорияИногдаКогда нужен std::mem_fn и когда его заменили лямбды?
Когда нужен std::mem_fn и когда его заменили лямбды?
std::mem_fn(&Class::method) адаптирует указатель на функцию-член в callable, принимающий объект первым. Полезен в STL-алгоритмах; современный код предпочитает лямбду.
Типичные ошибки
- ✗Использовать
std::bindтам, где лямбда понятнее — bind-композиция плохо читается - ✗Забывать, что
mem_fnпринимает и указатель, и ссылку, и автоматически выбирает - ✗Хранить результат
mem_fnв явном типе — на практике лучше auto
Уточняющие вопросы
- →Почему
std::bindпрактически вытеснен лямбдами? - →Как привязать часть аргументов и оставить другие свободными?
MiddleТеорияИногдаЧто такое trailing return type и когда он необходим?
Что такое trailing return type и когда он необходим?
auto f(args) -> ReturnType помещает тип возврата после параметров. Нужен, когда возврат зависит от параметров: auto add(T a, U b) -> decltype(a + b). Ещё нужен для SFINAE-friendly шаблонов.
Типичные ошибки
- ✗Просто
autoдля шаблонов, участвующих в overload resolution — вызывающий не может делать SFINAE по возврату - ✗Забывать, что
auto f() -> intидентичноint f()для не-шаблонов - ✗Смешивать
decltype(expr)с reference-collapsing в trailing return
Уточняющие вопросы
- →Чем
decltype(auto)отличается отautoв возвращаемом типе? - →Когда
auto-deduction в C++14 не работает и нужен-> T?
MiddleТеорияИногдаЧем C-style variadic-функции отличаются от variadic-шаблонов C++11?
Чем C-style variadic-функции отличаются от variadic-шаблонов C++11?
C-style ... с va_list не типобезопасен — callee «угадывает» типы через format string или sentinel; рассогласование — UB. Variadic-шаблоны C++11 template<class... A> типобезопасны: каждый аргумент сохраняет тип.
Типичные ошибки
- ✗Смешивать
va_listи variadic-шаблон в одном API - ✗Не использовать fold expressions:
(std::cout << ... << args)куда понятнее рекурсивной пары - ✗Передавать не-trivially-copyable через
va_list— UB
Уточняющие вопросы
- →Чем унарные fold expressions отличаются от бинарных?
- →Как
std::format(C++20) заменяет printf-style varargs безопасно?
SeniorТеорияИногдаКак noexcept(expr) даёт условный noexcept в шаблонах?
Как noexcept(expr) даёт условный noexcept в шаблонах?
noexcept(expr) вычисляет expr в compile-time и пробрасывает bool в noexcept-спецификацию. Шаблон: swap(T&, T&) noexcept(noexcept(swap(a.m, b.m))).
Типичные ошибки
- ✗Писать
noexcept(true)на функции, которая зовёт что-то бросающее — terminate в рантайме - ✗Забыть внешний
noexcept(...)и писатьnoexcept(swap(a, b))(это операнд!) — типичная опечатка - ✗Помечать move-ctor
noexcept, когда move члена бросает — terminate
Уточняющие вопросы
- →Почему стандартная библиотека требует noexcept-move для strong guarantee в vector?
- →Как вычисляется
is_nothrow_move_constructible_v?
SeniorПроизводительностьИногдаКогда компилятор делает tail-call optimisation в C++?
Когда компилятор делает tail-call optimisation в C++?
Стандарт C++ не требует TCO. GCC/Clang/MSVC делают tail-call, когда рекурсивный вызов — последняя операция при -O2/-O3, без деструкторов после и при совместимом ABI.
Типичные ошибки
- ✗Возвращать
std::move(recursive_call())— отключает TCO из-за обёртки - ✗Деструктор после рекурсивного вызова (локальная переменная с non-trivial dtor)
- ✗Компилировать в debug и удивляться отсутствию TCO
Уточняющие вопросы
- →Почему C++ не стандартизует TCO?
- →Как
[[clang::musttail]]меняет картину?
SeniorТеорияИногдаЧто такое операция INVOKE, и что унифицирует std::invoke?
Что такое операция INVOKE, и что унифицирует std::invoke?
INVOKE — абстрактное правило стандарта для вызова любого callable. std::invoke(f, args...) применяет его единообразно: свободные функции и функторы зовёт как f(args...), а для указателя на член трактует первый аргумент как объект, обрабатывая ссылки, указатели и reference_wrapper. На нём держатся std::bind, std::thread и std::function.
Типичные ошибки
- ✗Писать ветвящийся код для вызова свободной функции или указателя на член, когда
std::invokeуже унифицирует оба - ✗Считать, что
std::invokeдобавляет рантайм-стоимость — он полностью разрешается на компиляции и инлайнится - ✗Забывать, что
std::invoke_result_t/std::is_invocable_vопрашивают то же правило INVOKE в трейтах
Уточняющие вопросы
- →Чем
std::invoke_r(C++23) отличается отstd::invoke? - →Почему
std::bind,std::threadиstd::asyncзадают свой вызов через INVOKE?
SeniorТеорияРедкоЧто такое consteval и когда использовать его вместо constexpr?
Что такое consteval и когда использовать его вместо constexpr?
consteval (C++20) делает функцию immediate — каждый вызов обязан давать constant expression. В отличие от constexpr, fallback в рантайм невозможен. Для compile-time-only утилит.
Типичные ошибки
- ✗Пометить
constevalи пытаться вызвать с рантайм-аргументами - ✗Взять указатель на
consteval-функцию — ошибка - ✗Путать
constevalсconstinit(инициализирует переменную в compile-time)
Уточняющие вопросы
- →Как
constevalвзаимодействует сif consteval(C++23)? - →Зачем
consteval, еслиconstexprуже даёт compile-time?
SeniorТеорияРедкоЧто такое C++23 «deducing this» и какие задачи это решает?
Что такое C++23 «deducing this» и какие задачи это решает?
C++23 позволяет члену принимать this как явный параметр: void f(this Self&& self, ...). Шаблонизируется по value category и cv-квалификации — заменяет четыре перегрузки. Даёт CRTP без наследования.
Типичные ошибки
- ✗Пытаться
this->внутри deducing-this функции —thisнет, используйтеself. - ✗Помечать функцию
virtual— несовместимо - ✗Забывать forward: пишите
std::forward<Self>(self).memberдля сохранения value category
Уточняющие вопросы
- →Как deducing-this убирает необходимость в CRTP?
- →Почему deducing-this функция не может быть virtual?