Путеводитель C++ программиста по неопределенному поведению — увлекательное путешествие по тёмным уголкам C++.
(и еще пара ссылок ниже).
Автор несколько раз объясняет трудноуловимые ошибки в C++, через аналогичный код на Rust
, в котором borrow-чекер находит проблемы. В каком-то виде, видимо, проверки lifetime когда-нибудь подъедут и в C++.
Один из первых примеров:
void f(float&& x) { std::cout << "float " << x << "\n"; }
void f(int&& x) { std::cout << "int " << x << "\n"; }
void g(auto&& v) { f(v); } // C++20
// template <class T> void g(T v) { f(v); }
int main() {
g(2);
g(1.f);
}
// Выводит
// float 2
// int 1
void g(int&& v) {
// Несмотря на то что тип v — int&&
// Дальнейшее использование v в выражениях дает int& !
// decltype(v) == int&&
// decltype((v)) == int&
// Функции f принимают только rvalue ссылки
// Неявное преобразование int& к int&& запрещено
// int&& x = 5;
// int&& y = x; // не компилируется!
// Таким образом перегрузка f(int&&) не может быть использована
// Остается f(float&&)
// int умеет неявно приводиться к float
// int& умеет неявно выступать в роли просто int
// неявный static_cast<float>(v) возвращает временное значение float
// временные значения типа T неявно биндятся к T&&
// Имеем цепочку преобразований:
// int& -> int -> float -> float&&
f(v); // будет вызван f(float&&) !
// явно: f(static_cast<float>(v));
}
Семантический процесс приведения типов красиво стреляет в ногу (не так красиво как coersion в js, но и так неплохо получилось)
Константин Владимиров — Семантические процессы в C++
3 основных процесса:
- поиск и разрешение имён
- вывод и подстановка типов (проверка/приведение/вывод)
- инстанциирование шаблонов
Разобраться с каждым полезно для понимания языка. Кроме трюка с explicit
для запрета каста в конструкторах с одним параметром и операторах преобразования, можно еще перекрыть преобразование явным удалением всех функций, кроме единственной:
int only_ints(int x) { return x;}
template <class T>
auto only_ints(T x) = delete; //отключение преобразования
Мой любимый пример даже не UB, а IFNDR
— специализация шаблона не в заголовочном файле. Видел налёты на него раза три у знакомых программистов (последний раз в канале движка o2)
Магистерский курс C++ (МФТИ, 2022-2023). Лекция 3. Шаблоны классов и частичная специализация
Не копируйте! — фрик-статья о том, что ссылками в C++ сложно пользоваться (на самом деле, статьи с сайта можно давать на собеседованиях с вопросом “какие видишь логические ошибки в рассуждениях?”, и смотреть сколько кандидат насчитает).
Лучше всё-таки освоиться с Lifetime extension, чтобы представлять случаи, когда временный объект может прилипнуть к константной ссылке в неожиданном месте и ограничить время его жизни, так как продление жизни происходит один раз, и может быть использовано раньше, чем кажется, глядя на код.