Какой синтаксис ожидается в 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));
}
Я думаю, что в целом это меньше оборудования, чем вам понадобится для подхода на основе типов.