据我所知,静态反射目前在 C++26 的路线图上。
反射TS提出了基于类型的语法,但同时也提出了基于值的语法。在 P2560 Matúš Chochlı́k 中对这两种方法进行了比较。
是否已经决定哪种方法可能标准化?
C++ 委员会上周(2023 年 11 月)在科纳举行会议,并在第七研究组中对此进行了讨论。
C++26 反射的方向在P2996中概述,它是基于值的反射。高水平上:
^^e
是 e
的反映(可以是类型、模板、命名空间、表达式等)并且属于 std::meta::info
[: i :]
拼接 i
(基本上是反射的逆过程)。所以 [: ^^int :]
是类型 int
std::meta
命名空间中有一堆函数,它们接受 info
或一系列 info
并返回 info
或 vector<info
>SG7 一致批准了这个设计,没有兴趣讨论进一步追求 Reflection TS(即基于类型的反射)。
在弗罗茨瓦夫会议(2024 年 11 月)中,语法根据 P3380 略有调整。最初的反射算子是
^e
,现在是 ^^e
。此更改是由于 Clang Blocks 的歧义而进行的,并且已通过 P2996 传播。
就 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>
的反映。后一个示例在基于类型的反射中也很容易实现,只是您可以使用 Boost.Mp11 等中的 std::ranges::transform
来代替 mp_transform
。但这是一种完全不同的子语言 - 这就是为什么我质疑该论文中列出的优点?
论文还指出了
count_if
不起作用。确实,这将是一个人们必须了解的微妙案例:
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_type);
}
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 extract<bool>(substitute(^^my_trait, {type}));
});
}
这篇论文是正确的,这是不一致的 - 尽管值得注意的是我们仍然可以对两者使用
count_if
,我认为这使得它成为对泛型编程更友好且更易于教学的方法。
我们总能想出办法让这一切变得更好。喜欢:
consteval auto trait_to_pred(std::meta::info trait) {
return [=](auto... args){
return extract<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));
}
我认为,总的来说,这比基于类型的方法所需的机械要少。