Содержание
В обзоре «Что нового в C++26» контракты и рефлексия заняли по одному абзацу. Это два самых крупных новшества стандарта, и по короткому описанию трудно понять, как ими пользоваться. В этой статье — примеры, которые можно собрать и запустить уже сейчас.
Все программы проверены компилятором GCC 16.2. Сообщения о нарушениях контрактов и вывод программ приведены такими, какими мы их получили.
Как попробовать
Проще всего — в браузере. Откройте Compiler Explorer (godbolt.org), выберите компилятор x86-64 gcc 16.2 и впишите флаги:
-std=c++26 -fcontracts -freflection
Если GCC 16 установлен на компьютере, команда выглядит так:
g++ -std=c++26 -fcontracts -freflection main.cpp -o main
Обе возможности включаются отдельными флагами: -fcontracts — контракты, -freflection — рефлексия. В GCC они пока помечены как экспериментальные.
Контракты
Идея
У любой функции есть условия, при которых она работает правильно. Среднее арифметическое не посчитать для пустого списка, корень не извлечь из отрицательного числа. Обычно такие условия живут в комментарии или в голове автора. Контракты позволяют записать их прямо в объявлении функции.
#include <iostream>
#include <vector>
double average(const std::vector<int>& scores)
pre(!scores.empty())
post(result: result >= 0)
{
long long sum = 0;
for (int score : scores) {
contract_assert(score >= 0);
sum += score;
}
return static_cast<double>(sum) / scores.size();
}
int main() {
std::cout << average({5, 4, 3}) << '\n';
std::cout << average({}) << '\n';
std::cout << "конец программы\n";
return 0;
}
Здесь три вида проверок:
pre(условие)— предусловие: что должно быть верно в момент вызова. Отвечает за него тот, кто вызывает функцию.post(result: условие)— постусловие: что функция обещает вернуть. Имяresultобозначает возвращаемое значение, его можно назвать как угодно.contract_assert(условие)— проверка внутри тела функции.
Что происходит при нарушении
Во втором вызове список пуст, и предусловие нарушено. Программа, собранная с -fcontracts, выводит первое значение и останавливается:
4
contract violation in function double average(const std::vector<int>&) at main.cpp:5: !scores.empty()
[assertion_kind: pre, semantic: enforce, mode: predicate_false, terminating: yes]
terminate called without an active exception
В сообщении есть всё нужное: функция, строка, само условие и вид проверки.
Четыре режима
Главное отличие контрактов от привычного assert: что делать при нарушении, решается при сборке, а не в коде. Режимов четыре, в GCC они выбираются флагом -fcontract-evaluation-semantic=.
| Режим | Проверяет условие | Сообщает о нарушении | Останавливает программу |
|---|---|---|---|
ignore | нет | нет | нет |
observe | да | да | нет |
enforce | да | да | да |
quick_enforce | да | нет | да |
Та же программа в режиме observe сообщает о нарушениях и работает дальше:
4
contract violation in function double average(const std::vector<int>&) at main.cpp:5: !scores.empty()
[assertion_kind: pre, semantic: observe, mode: predicate_false, terminating: no]
contract violation in function double average(const std::vector<int>&) at main.cpp:6: result >= 0
[assertion_kind: post, semantic: observe, mode: predicate_false, terminating: no]
-nan
конец программы
Нарушений два: пустой список и результат nan, который не проходит проверку result >= 0. Режим observe удобен, когда контракты добавляют в большую работающую программу и хотят сначала увидеть все нарушения, ничего не сломав.
В режиме ignore условия не вычисляются совсем, и программа просто выводит -nan.
Правило, о которое спотыкаются все
Попробуем написать постусловие, которое сравнивает результат с параметром:
int next_level(int level)
post(result: result > level)
{
return level + 1;
}
Компилятор откажется:
error: a value parameter used in a postcondition must be const
Причина разумная. Параметр, переданный по значению, можно изменить внутри функции, и тогда постусловие сравнивало бы результат уже не с тем, что передал вызывающий. Поэтому такой параметр обязан быть константой:
int next_level(const int level)
post(result: result > level)
{
return level + 1;
}
На параметры-ссылки и на предусловия правило не распространяется.
Чем контракт не является
- Это не проверка пользовательского ввода. Если число вводит человек, проверяйте его обычным
ifи сообщайте об ошибке. Контракт описывает ошибку программиста: функцию вызвали не так, как положено. - Это не обработка ошибок. В режиме
ignoreпроверки исчезают, поэтому программа не должна на них полагаться. - В условии не должно быть побочных действий. Условие может быть вычислено один раз, несколько раз или ни разу — менять в нём переменные нельзя.
Рефлексия
Идея
Рефлексия — способность программы во время компиляции узнавать собственное устройство: какие значения есть у перечисления, какие поля у структуры и как они называются. Для этого в языке появились два новых обозначения.
^^превращает имя в значение, с которым можно работать.^^int— это «сведения о типе int».[: :]делает обратное: превращает такое значение снова в код.
#include <iostream>
#include <meta>
int main() {
constexpr auto type = ^^int;
typename[:type:] x = 42; // то же, что int x = 42;
std::cout << x << ' ' << std::meta::display_string_of(type) << '\n';
return 0;
}
Программа выводит 42 int. Сам по себе пример бесполезен, но на этих двух операциях построено всё остальное. Функции для работы со сведениями лежат в заголовке <meta>.
Перечисление в строку
Вывести название значения enum — задача, с которой сталкивается каждый. До C++26 для неё писали switch на все значения и не забывали дополнять его при каждом изменении перечисления. Теперь функцию можно написать один раз для любого перечисления:
#include <iostream>
#include <meta>
#include <string_view>
enum class Color { red, green, blue };
template <typename E>
constexpr std::string_view enum_to_string(E value) {
template for (constexpr auto e : std::define_static_array(std::meta::enumerators_of(^^E))) {
if (value == [:e:]) {
return std::meta::identifier_of(e);
}
}
return "<unknown>";
}
int main() {
std::cout << enum_to_string(Color::green) << '\n';
std::cout << enum_to_string(Color::blue) << '\n';
return 0;
}
Вывод: green и blue. Разберём по частям.
^^E— сведения о перечислении.std::meta::enumerators_of(...)— список его значений.template for— ещё одно новшество C++26, «цикл времени компиляции». Он не выполняется при работе программы: компилятор подставляет тело цикла для каждого значения отдельно.[:e:]— превращает сведения о значении обратно в само значение, чтобы его можно было сравнить.std::meta::identifier_of(e)— имя значения в виде строки.
Обратное преобразование, из строки в перечисление, пишется так же:
template <typename E>
constexpr std::optional<E> string_to_enum(std::string_view name) {
template for (constexpr auto e : std::define_static_array(std::meta::enumerators_of(^^E))) {
if (name == std::meta::identifier_of(e)) {
return [:e:];
}
}
return std::nullopt;
}
Поля структуры
Тот же приём работает для структур. Функция ниже печатает имена и значения всех полей любой структуры:
#include <iostream>
#include <meta>
#include <string>
struct Student {
std::string name;
int grade;
double average;
};
template <typename T>
void print_fields(const T& object) {
constexpr auto context = std::meta::access_context::current();
template for (constexpr auto member :
std::define_static_array(std::meta::nonstatic_data_members_of(^^T, context))) {
std::cout << std::meta::identifier_of(member) << " = " << object.[:member:] << '\n';
}
}
int main() {
Student student{"Анна", 9, 4.8};
print_fields(student);
return 0;
}
name = Анна
grade = 9
average = 4.8
Запись object.[:member:] — обращение к полю, которое выбрано не по имени в коде, а через рефлексию. Добавьте в структуру четвёртое поле, и функция напечатает его без единой правки.
На этом приёме строятся сохранение объектов в JSON, сравнение структур по полям, проверка заполненности форм — всё то, для чего раньше использовали макросы или отдельные программы-генераторы кода.
Поддержка в компиляторах
Состояние на октябрь 2026 года.
| Возможность | GCC | Clang | Visual Studio |
|---|---|---|---|
| Контракты | 16, флаг -fcontracts | в выпусках нет | не отмечена |
| Рефлексия | 16, флаг -freflection | в выпусках нет | не отмечена |
template for | 16 | в выпусках нет | не отмечена |
GCC 16.1 вышел 30 апреля 2026 года и стал первым из распространённых компиляторов, в выпуске которого есть обе возможности. Для Clang существуют экспериментальные ветки с рефлексией и контрактами, но в основные выпуски они на эту дату не вошли. Данные взяты из таблицы cppreference и заметок о выпуске GCC; ситуация меняется быстро, поэтому перед началом работы сверяйтесь с первоисточником.
Нужно ли это начинающему
Рефлексию — пока нет. Это инструмент для авторов библиотек, и пользоваться её плодами можно, не зная, как она устроена.
Контракты — стоит хотя бы как способ думать. Вопросы «при каких условиях функцию можно вызывать?» и «что она обещает вернуть?» полезно задавать себе про любую функцию, даже если Ваш компилятор слов pre и post ещё не понимает.
Основа языка в C++26 осталась прежней. Если Вы только начинаете, идите по порядку: урок учебника «Введение в C++: история, компиляция и первая программа» подойдёт для любого стандарта.
Источники
- P2900R14: Contracts for C++ [Электронный ресурс]. — URL: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p2900r14.pdf (дата обращения: 10.10.2026).
- P2996R13: Reflection for C++26 [Электронный ресурс]. — URL: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p2996r13.html (дата обращения: 10.10.2026).
- GCC 16 Release Series: Changes, New Features, and Fixes [Электронный ресурс]. — URL: https://gcc.gnu.org/gcc-16/changes.html (дата обращения: 10.10.2026).
- C++26 compiler support — cppreference.com [Электронный ресурс]. — URL: https://en.cppreference.com/w/cpp/compiler_support/26 (дата обращения: 10.10.2026).
- C++26 is done! Trip report: March 2026 ISO C++ standards meeting — Herb Sutter [Электронный ресурс]. — URL: https://herbsutter.com/2026/03/29/c26-is-done-trip-report-march-2026-iso-c-standards-meeting-london-croydon-uk/ (дата обращения: 10.10.2026).



