C++17 -> C++20



По традиции, рассказываю, что может пойти не так, когда вы хотите переехать с C++17 на C++20. В среднем вы не должны ничего заметить, но различий достаточно, чтобы заглянуть в страшные уголки плюсов.



От самых страшных до не самых страшных



1. Spaceship operator<=>



Единственная фича, которая уменьшила количество слов в стандарте и добавила головной боли компиляторным инженерам. Сломаться может интересно: если у типа есть implicit оператор вида





struct Int {

int i;

operator bool() const {

// Returns true if not 0, false if 0.

return i;

}

};



Тогда <=> оператор в первую очередь применит оператор меньше к кастуемому типу, в итоге



std::pair{Int{1}, Int{2}} < std::pair{Int{3}, Int{4}}



вернёт 1 в C++17, 0 в C++20.



Достаточно опасно, так как сильно меняет семантику. Как чинить? Писать explicit operator. Пример падения в проде.



2. operator== ambiguity





struct B {};

struct D : B {

bool operator==(const B& other);

};

D d;

bool eq = d == d; // работает в C++17, но не в C++20



На самом деле та же проблема. Бьярне и компания написали целую статью про это тут. Если коротко, то в C++20 написали правила, чтобы операторы == и != вели себя логичным образом, что !(a == b) это a != b. С другой стороны это создаёт проблему в духе





struct S {

friend bool f(S, const S&) { ... }

friend bool f(const S&, S) { ... }

};



bool b = f(S{}, S{});



И в итоге operator== и operator!= считаются одними и те же функциями. Это поломало много проектов, поэтому компиляторы быстро взяли фикс из C++23, и проблем больше нет https://godbolt.org/z/7e4E6qhbM.



3. Всякие проблемы с std::string_view против std::vector<std::string*like> и тд



Конструкция {"a", "b"} вызывает лёгкий ужас, так как в C++20 в string_view добавили конструктор от 2 итераторов и "a" и "b" являются const char* start и const char* end, что очень плохо заканчивается: вы читаете чёрт пойми какую память.



std::vector<std::string_view>{{"a", "b"}}



Теперь создаёт палёный string_view из локации, где "a" -- начало, потом где "b" -- конец, а в C++17 создавало 2 строки. Санитайзеры не ловят. https://gcc.godbolt.org/z/jboa5rbfa. Самое ядро заключается в том, что {"a", "b"} создают не initializer_list, а обычную string_view



Зато можно всякого интересного придумать.



Например, можно добавить в libc++ код в духе





template <int M, int N>

constexpr basic_string_view(char_type (&a)[M], char_type (&b)[N]) : basic_string_view() {

_LIBCPP_ASSERT(&a == &b);

}



И он будет падать в рантайме, в санитайзерах, орать warningами и быть ещё standard compliant. Выстрел в ногу, но всё таки элегантно чинится.



4. Всё остальное кажется достаточно минорным, атомики перестали быть trivially constructible, поэтому их в union не очень стоит класть, добавили слова concept, requires, добавились constexpr контейнеры, поэтому все шаблоны вектора должны быть полными типами. Стали делать std::move на начальное значение в std::accumulate, поэтому функция аккумулирования перестала принимать lvalue references. Aggregates теперь не компилируются, если есть конструкторы. Всё это чинится проходом по всему и достаточно прямолинейно.



Я думаю по сложности C++14 -> C++17 был проще, чем C++17 -> C++20, но тоже со своими интересными решениями, как noexcept как часть типа и тд. Всё равно суммарно набирается работы, хоть и не так много.



Самая интересная часть, которую пока никто не знает наверняка, насколько дольше стали компилироваться проекты.