Рефлексия и контракты C++26 на примерах: что уже работает в компиляторах

Два главных новшества C++26 в работающем коде: контракты pre, post и contract_assert с четырьмя режимами проверки и рефлексия — перевод enum в строку и печать полей структуры без макросов. Все примеры собраны в GCC 16; таблица поддержки в компиляторах на октябрь 2026 года.

Содержание

В обзоре «Что нового в C++26» контракты и рефлексия заняли по одному абзацу. Это два самых крупных новшества стандарта, и по короткому описанию трудно понять, как ими пользоваться. В этой статье — примеры, которые можно собрать и запустить уже сейчас.

Все программы проверены компилятором GCC 16.2. Сообщения о нарушениях контрактов и вывод программ приведены такими, какими мы их получили.

Как попробовать

Проще всего — в браузере. Откройте Compiler Explorer (godbolt.org), выберите компилятор x86-64 gcc 16.2 и впишите флаги:

TXT
-std=c++26 -fcontracts -freflection

Если GCC 16 установлен на компьютере, команда выглядит так:

Bash
g++ -std=c++26 -fcontracts -freflection main.cpp -o main

Обе возможности включаются отдельными флагами: -fcontracts — контракты, -freflection — рефлексия. В GCC они пока помечены как экспериментальные.

Контракты

Идея

У любой функции есть условия, при которых она работает правильно. Среднее арифметическое не посчитать для пустого списка, корень не извлечь из отрицательного числа. Обычно такие условия живут в комментарии или в голове автора. Контракты позволяют записать их прямо в объявлении функции.

C++
#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, выводит первое значение и останавливается:

TXT
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 сообщает о нарушениях и работает дальше:

TXT
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.

Правило, о которое спотыкаются все

Попробуем написать постусловие, которое сравнивает результат с параметром:

C++
int next_level(int level)
    post(result: result > level)
{
    return level + 1;
}

Компилятор откажется:

TXT
error: a value parameter used in a postcondition must be const

Причина разумная. Параметр, переданный по значению, можно изменить внутри функции, и тогда постусловие сравнивало бы результат уже не с тем, что передал вызывающий. Поэтому такой параметр обязан быть константой:

C++
int next_level(const int level)
    post(result: result > level)
{
    return level + 1;
}

На параметры-ссылки и на предусловия правило не распространяется.

Чем контракт не является

  • Это не проверка пользовательского ввода. Если число вводит человек, проверяйте его обычным if и сообщайте об ошибке. Контракт описывает ошибку программиста: функцию вызвали не так, как положено.
  • Это не обработка ошибок. В режиме ignore проверки исчезают, поэтому программа не должна на них полагаться.
  • В условии не должно быть побочных действий. Условие может быть вычислено один раз, несколько раз или ни разу — менять в нём переменные нельзя.

Рефлексия

Идея

Рефлексия — способность программы во время компиляции узнавать собственное устройство: какие значения есть у перечисления, какие поля у структуры и как они называются. Для этого в языке появились два новых обозначения.

  • ^^ превращает имя в значение, с которым можно работать. ^^int — это «сведения о типе int».
  • [: :] делает обратное: превращает такое значение снова в код.
C++
#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 на все значения и не забывали дополнять его при каждом изменении перечисления. Теперь функцию можно написать один раз для любого перечисления:

C++
#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) — имя значения в виде строки.

Обратное преобразование, из строки в перечисление, пишется так же:

C++
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;
}

Поля структуры

Тот же приём работает для структур. Функция ниже печатает имена и значения всех полей любой структуры:

C++
#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;
}
TXT
name = Анна
grade = 9
average = 4.8

Запись object.[:member:] — обращение к полю, которое выбрано не по имени в коде, а через рефлексию. Добавьте в структуру четвёртое поле, и функция напечатает его без единой правки.

На этом приёме строятся сохранение объектов в JSON, сравнение структур по полям, проверка заполненности форм — всё то, для чего раньше использовали макросы или отдельные программы-генераторы кода.

Поддержка в компиляторах

Состояние на октябрь 2026 года.

ВозможностьGCCClangVisual Studio
Контракты16, флаг -fcontractsв выпусках нетне отмечена
Рефлексия16, флаг -freflectionв выпусках нетне отмечена
template for16в выпусках нетне отмечена

GCC 16.1 вышел 30 апреля 2026 года и стал первым из распространённых компиляторов, в выпуске которого есть обе возможности. Для Clang существуют экспериментальные ветки с рефлексией и контрактами, но в основные выпуски они на эту дату не вошли. Данные взяты из таблицы cppreference и заметок о выпуске GCC; ситуация меняется быстро, поэтому перед началом работы сверяйтесь с первоисточником.

Нужно ли это начинающему

Рефлексию — пока нет. Это инструмент для авторов библиотек, и пользоваться её плодами можно, не зная, как она устроена.

Контракты — стоит хотя бы как способ думать. Вопросы «при каких условиях функцию можно вызывать?» и «что она обещает вернуть?» полезно задавать себе про любую функцию, даже если Ваш компилятор слов pre и post ещё не понимает.

Основа языка в C++26 осталась прежней. Если Вы только начинаете, идите по порядку: урок учебника «Введение в C++: история, компиляция и первая программа» подойдёт для любого стандарта.

Источники

  1. P2900R14: Contracts for C++ [Электронный ресурс]. — URL: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p2900r14.pdf (дата обращения: 10.10.2026).
  2. P2996R13: Reflection for C++26 [Электронный ресурс]. — URL: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p2996r13.html (дата обращения: 10.10.2026).
  3. GCC 16 Release Series: Changes, New Features, and Fixes [Электронный ресурс]. — URL: https://gcc.gnu.org/gcc-16/changes.html (дата обращения: 10.10.2026).
  4. C++26 compiler support — cppreference.com [Электронный ресурс]. — URL: https://en.cppreference.com/w/cpp/compiler_support/26 (дата обращения: 10.10.2026).
  5. 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).
C++Компилятор