Какой синтаксис ожидается в C++26 для статического отражения?

Насколько мне известно, статическое отражение в настоящее время включено в план развития C++26.

TS отражения предлагает синтаксис на основе типов, но в то же время был предложен и синтаксис на основе значений. В P2560 Матуш Чочлик представил сравнение обоих подходов.

Принято ли уже решение о том, какой подход будет стандартизирован?

1 ответ

Комитет C++ собрался в Коне на прошлой неделе (ноябрь 2023 г.), и это обсуждалось на SG7.

Направление Reflection для C++26 изложено в P2996 (в настоящее время R0, R1 будет опубликован на следующей неделе), который представляет собой отражение на основе значений. На высоком уровне:

  • ^eявляется отражениемe(который может быть типом, шаблоном, пространством имен, выражением и т. д.) и имеет тип
  • [: i :]сращиванияi(и по сути является обратным отражению). Так[: ^int :]это типint
  • там куча функцийstd::metaпространство имен, которое принимает илиspan<info const>и вернутьсяinfoилиvector<info>

ИК7 единогласно одобрила этот проект, не было никакого интереса к обсуждению дальнейшего развития Reflection TS (т.е. отражения на основе типов).


Что касается некоторых комментариев к P2560, я не согласен с некоторыми из высказанных моментов, в частности с аргументами о том, что «плюсами» основанного на типах являются то, что он «более удобен в использовании», «его легче обучать» и «просто обучается». более дружелюбен к общему программированию».

Я просто отмечу один из примеров в аналитическом документе, который должен реализоватьmake_integer_sequence. Цель здесь в том, чтобыmake_integer_sequence<int, 5>создает экземплярыinteger_sequence<int, 0, 1, 2, 3, 4>. Как это реализовать в рефлексии, основанной на ценностях?

      template<typename T>
consteval std::meta::info make_integer_seq_refl(T N) {
  std::vector<std::meta::info> args{^T};
  for (T k = 0; k < N; ++k) {
    args.push_back(std::meta::reflect_value(k));
  }
  return substitute(^std::integer_sequence, args);
}

template<typename T, T N>
  using make_integer_sequence = [:make_integer_seq_refl(N):];

Здесь,substitute— это API отражения, который принимает отражение шаблона и диапазон отражений параметров и возвращает отражение экземпляра этого шаблона. Например,substitute(^std::tuple, {^int, ^char})дает тебе^std::tuple<int, char>, за исключением того, что он позволяет вам жить в области ценностей. Это правда, что вам придется узнать, что это такое, но в остальном — это довольно простой алгоритм: вы делаетеvector, и вы нажимаете на него что-нибудь. В этом случае наш «материал» неоднороден, поскольку у нас есть один аргумент шаблона типа и несколько аргументов шаблона нетипового типа, и это работает нормально, посколькуstd::meta::infoэто единственный тип. Как бы вы реализовали это в подходе на основе типов? Проблема метапрограммирования на основе типов заключается в том, что оно не может быть императивным — оно должно быть функциональным.

Это правда, что, как указывает P2560, сращивание требует наличия константного выражения для обратного сращивания. И это, безусловно, влияет на то, как вам приходится что-то программировать. Но наличие богатого API означает, что вы можете оставаться в ценностной области дольше, чем вы думаете, и это позволяет вам использовать остальную часть API стандартной библиотеки для выполнения своей работы. Например:

      consteval auto struct_to_tuple_type(info type) -> info {
  return substitute(^std::tuple,
                    nonstatic_data_members_of(type)
                    | std::ranges::transform(std::meta::type_of)
                    | std::ranges::transform(std::meta::remove_cvref)
                    | std::ranges::to<std::vector>());
}

Учитывая что-то вродеstruct S { int a; char& b; };,struct_to_tuple_type(^S)даст вам отражениеstd::tuple<int, char>. Этот последний пример очень легко выполним и при отражении на основе типов, просто вместоstd::ranges::transformты бы использовалmp_transformиз Boost.Mp11 и так далее. Но это совершенно отдельный подъязык — поэтому я подвергаю сомнению плюсы, перечисленные в этой статье?

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

      template <typename T> inline constexpr bool my_trait = /* ... */;

consteval auto num_const(span<info const> some_types) -> bool {
    // this one is built-in, so easy
    return std::ranges::count_if(some_types, std::meta::is_const);
}

consteval auto num_my_trait(span<info const> some_types) -> bool {
    // this one requires this one weird trick
    return std::ranges::count_if(some_types, [](std::meta::info type){
        // first, we need my_trait<T>, which is a substitute
        // then, we need to pull a value out of it, which we need to declare as bool
        return value_of<bool>(substitute(^my_trait, {type}));
    });
}

В статье верно, что это противоречиво, хотя, в частности, мы все еще можем использоватьcount_ifдля обоих, что, я бы сказал, делает этот подход более дружественным к общему программированию и более простым в обучении.

И мы всегда можем найти способы сделать это лучше. Нравиться:

      consteval auto trait_to_pred(std::meta::info trait) {
    return [=](auto... args){
        return value_of<bool>(substitute(trait, {args...}));
    });
}

consteval auto num_my_trait(span<info const> some_types) -> bool {
    return std::ranges::count_if(some_types, trait_to_pred(^my_trait));
}

Я думаю, что в целом это меньше оборудования, чем вам понадобится для подхода на основе типов.

Другие вопросы по тегам