C++26中静态反射的语法是什么?

kmbjn2e3  于 11个月前  发布在  其他
关注(0)|答案(1)|浏览(186)

据我所知,静态反射目前在C++26的路线图上。
反射TS提出了一种基于类型的语法,但同时也提出了一种基于值的语法。在P2560 MatústanChochllok中,他对这两种方法进行了比较。
是否已经决定哪种方法可能被标准化?

ao218c7q

ao218c7q1#

C委员会上周(2023年11月)在科纳举行了会议,这在第7研究组中进行了讨论。
C
26反射的方向在P2996中概述(目前R 0,R1将于下周发布),这是一个基于值的反射。在高级别上:

  • ^ee的反射(可以是类型、模板、命名空间、表达式等),类型为std::meta::info
  • [: i :]拼接i(基本上是反射的逆)。所以[: ^int :]int类型
  • std::meta命名空间中有一堆函数,它们接受infospan<info const>并返回infovector<info>

第七研究组一致通过了这个设计,没有兴趣讨论进一步追求反射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>的反射。后一个例子在基于类型的反射中也很容易做到,只是你会使用Boost.Mp11等等的mp_transform来代替std::ranges::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);
}

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


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

相关问题