C++26 中静态反射需要什么语法?

问题描述 投票:0回答:1

据我所知,静态反射目前在 C++26 的路线图上。

反射TS提出了基于类型的语法,但同时也提出了基于值的语法。在 P2560 Matúš Chochlı́k 中对这两种方法进行了比较。

是否已经决定哪种方法可能标准化?

c++ reflection static-reflection c++26
1个回答
10
投票

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));
}

我认为,总的来说,这比基于类型的方法所需的机械要少。

© www.soinside.com 2019 - 2024. All rights reserved.