赞
踩
在此体验.
只需要包含#include <experimental/meta>
.
#include <experimental/meta>
int main() {
constexpr auto r = ^int;
typename[:r:] x = 42;
// ==: int x = 42;
typename[:^char:] c = '*';
// ==: char c = '*';
}
这是一个操作成员的小示例:
struct S { unsigned i:2, j:6; };
consteval auto member_number(int n) {
if (n == 0) return ^S::i;
else if (n == 1) return ^S::j;
}
int main() {
S s{0, 0};
s.[:member_number(1):] = 42;
//等同于:`s.j=42`;
s.[:member_number(5):] = 0;
//错误`(member_number(5)`不是常量).
}
通过提升(reflection)operator
先返回meta::info
反射类型,再通过splicing
重新得到成员类型,从而访问成员.
constexpr std::array types = {^int, ^float, ^double};
//这里需要`consteval`,因为尚未实现`consteval`传播`(P2564)`
constexpr std::array sizes = []() consteval {
std::array<std::size_t, types.size()> r;
std::transform(types.begin(), types.end(), r.begin(), std::meta::size_of);
return r;
}();
static_assert(sizes[0] == sizeof(int));
static_assert(sizes[1] == sizeof(float));
static_assert(sizes[2] == sizeof(double));
该示例
同样很简单,不多讲,最终大小
的内容就相当于:
std::array<std::size_t, 3> sizes = {sizeof(int), sizeof(float), sizeof(double)};
通过反射
来简化
实现make_integer_sequence
:
template<typename T>
consteval std::meta::info make_integer_seq_refl(T N) {
std::vector 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<T>(N):];
static_assert(std::same_as<
make_integer_sequence<int, 10>,
std::make_integer_sequence<int, 10>
>);
该实现逻辑
也比较清晰,主要涉及两个reflect_value
和substitude
元函数.
其中,reflect_value
的声明为:
namespace std::meta {
template<typename T>
consteval auto reflect_value(T const&)->info;
template<typename R>
consteval auto reflect_values(R const&)->std::span<info>;
}
这两个元函数
来把Constantvalue(s)提升
为(meta::info)
反射类型表示,如:
constexpr std::vector<int> v{ 1, 2, 3 };
constexpr std::span<std::meta::info> rv = reflect_values(v);
随后,便可把该提升序列
重新Splicing
出来,如按模板参数
使用:
std::integer_sequence<int, ...[:rv:]...> is123;
//与std::integer_sequence<int,1,2,3>相同
以上仅是示例,EDGReflection
尚不支持reflect_values
,只支持reflect_value
.
因此,
args.push_back(std::meta::reflect_value(k));
的意思,就是生成一个常数序列
,再通过生成
的序列创建一个std::integer_sequence
,需要用到如下标准声明的substitute
元函数:
namespace std::meta {
consteval auto substitute(info templ, std::span<info> args)
->info { ... };
}
功能
是根据已有类型,参数
,生成新的类型
.一例:
using namespace std::meta;
template<typename ... Ts> struct X {};
template<> struct X<int, int> {};
constexpr info type = ^X<int, int, float>;
constexpr info templ = template_of(type);
constexpr span<info> args = template_arguments_of(type);
constexpr info new_type = substitute(templ, args.subspan(0, 2));
typename[:new_type:] xii; //`X<int,int>`类型来选择`特化`.不能实例化`显式/部分特化`取代的`主模板定义`.
根据X<int,int,float>
生成了新的X<int,int>
类型.
但是,EDG
目前有些局限,它使用std::vector<info>
来代替std::span<info const>
,因此
substitute(^std::integer_sequence, args);
中才使用std::vector<info>
参数.
struct member_descriptor { std::size_t offset; std::size_t size; bool operator==(member_descriptor const&) const = default; }; //返回`std::array<member_descriptor,N>` template <typename S> consteval auto get_layout() { constexpr size_t N = []() consteval { return nonstatic_data_members_of(^S).size(); }(); std::array<member_descriptor, N> layout; [: expand(nonstatic_data_members_of(^S)) :] >> [&, i=0]<auto e>() mutable { layout[i] = {.offset=offset_of(e), .size=size_of(e)}; ++i; }; return layout; } struct X { char a; int b; double c; }; constexpr auto Xd = get_layout<X>(); static_assert(Xd.size() == 3); static_assert(Xd[0] == member_descriptor{.offset=0, .size=1}); static_assert(Xd[1] == member_descriptor{.offset=4, .size=4}); static_assert(Xd[2] == member_descriptor{.offset=8, .size=8});
get_layout()
是主要逻辑点
,来取类型
的非静态数据成员信息
,在member_descriptor
里面保存信息.
因为EDG
目前不支持扩展语句
,所以增加了一些实现的复杂度.如果使用扩展语句
,核心语句实现可这样:
std::array<member_descriptor, N> layout;
int i = 0;
template for (constexpr auto e : std::meta::nonstatic_data_members_of(^S)) {
layout[i] = {.offset=offset_of(e), .size=size_of(e)};
++i;
}
expand()
是EDG
对扩展语句
的临时平替
,实现为:
namespace __impl { template<auto... vals> struct replicator_type { template<typename F> constexpr void operator>>(F body) const { (body.template operator()<vals>(), ...); } }; template<auto... vals> replicator_type<vals...> replicator = {}; } template<typename R> consteval auto expand(R range) { std::vector<std::meta::info> args; for (auto r : range) { args.push_back(reflect_value(r)); } return substitute(^__impl::replicator, args); }
示例
中其他使用的元函数
皆这样,逻辑清晰,不必多讲.
最经典的示例,相当于反射界的你好,世界
.
最经典
的当属标准版本
:
template <typename E>
requires std::is_enum_v<E>
constexpr std::string enum_to_string(E value) {
template for (constexpr auto e : std::meta::members_of(^E)) {
if (value == [:e:]) {
return std::string(std::meta::name_of(e));
}
}
return "<unnamed>";
}
enum Color { red, green, blue };
static_assert(enum_to_string(Color::red) == "red");
static_assert(enum_to_string(Color(42)) == "<unnamed>");
及反操作
版本:
template <typename E>
requires std::is_enum_v<E>
constexpr std::optional<E> string_to_enum(std::string_view name) {
template for (constexpr auto e : std::meta::members_of(^E)) {
if (name == std::meta::name_of(e)) {
return [:e:];
}
}
return std::nullopt;
}
但是EDG
不支持扩展语句
,所以使用expand()
代替:
template<typename E>
requires std::is_enum_v<E>
constexpr std::string enum_to_string(E value) {
std::string result = "<unnamed>";
[:expand(std::meta::enumerators_of(^E)):] >>
[&]<auto e>{
if (value == [:e:]) {
result = std::meta::name_of(e);
}
};
return result;
}
enum Color { red, green, blue };
static_assert(enum_to_string(Color::red) == "red");
static_assert(enum_to_string(Color(42)) == "<unnamed>");
该实现的复杂度为O(N)
,他们提供了另一个利用Ranges
算法只需要O(log(N))
复杂度的实现:
template <typename E> requires std::is_enum_v<E> constexpr std::string enum_to_string(E value) { constexpr auto enumerators = std::meta::members_of(^E) | std::views::transform([](std::meta::info e){ return std::pair<E, std::string>(std::meta::value_of<E>(e), std::meta::name_of(e)); }) | std::ranges::to<std::map>(); auto it = enumerators.find(value); if (it != enumerators.end()) { return it->second; } else { return "<unnamed>"; } }
这样借助std::map
来实现.
与传统递归继承
实现相比,更简单的Tuple
实现法:
namespace std::meta { consteval auto make_nsdm_description(info type, nsdm_options options = {}) { return nsdm_description(type, options); } } template<typename... Ts> struct Tuple { struct storage; static_assert(is_type(define_class(^storage, {make_nsdm_description(^Ts)...}))); storage data; Tuple(): data{} {} Tuple(Ts const& ...vs): data{ vs... } {} }; template<typename... Ts> struct std::tuple_size<Tuple<Ts...>>: public integral_constant<size_t, sizeof...(Ts)> {}; template<std::size_t I, typename... Ts> struct std::tuple_element<I, Tuple<Ts...>> { static constexpr std::array types = {^Ts...}; using type = [: types[I] :]; }; consteval std::meta::info get_nth_nsdm(std::meta::info r, std::size_t n) { return nonstatic_data_members_of(r)[n]; } template<std::size_t I, typename... Ts> constexpr auto get(Tuple<Ts...> &t) noexcept -> std::tuple_element_t<I, Tuple<Ts...>>& { return t.data.[:get_nth_nsdm(^decltype(t.data), I):]; } template<std::size_t I, typename... Ts> constexpr auto get(Tuple<Ts...> const&t) noexcept -> std::tuple_element_t<I, Tuple<Ts...>> const& { return t.data.[:get_nth_nsdm(^decltype(t.data), I):]; } template<std::size_t I, typename... Ts> constexpr auto get(Tuple<Ts...> &&t) noexcept -> std::tuple_element_t<I, Tuple<Ts...>> && { return std::move(t).data.[:get_nth_nsdm(^decltype(t.data), I):]; } int main() { auto [x, y, z] = Tuple{1, 'c', 3.14}; assert(x == 1); assert(y == 'c'); assert(z == 3.14); }
这样实现的关键
在于生成代码
,而EDG
当前并不支持注入源码
,所以提供了丐版
的std::meta::nsdm_description
和std::meta::define_class
替代元函数,来允许合成简单的struct/union
类型.声明为:
namespace std::meta {
struct nsdm_options_t {
optional<string_view> name;
optional<int> alignment;
optional<int> width;
};
consteval auto nsdm_description(info type, nsdm_options options = {}) -> info;
consteval auto define_class(info class_type, span<info const>) -> info;
}
nsdm_description
返回给定类型
非静态数据成员的反射描述信息
,nsdm_options_t
指定数据成员
的比如名,对齐和宽度
等额外信息
,
而define_class
接受一个不完整的class/struct/union
和非静态
数据成员的反射元信息序列
(由nsdm_description
的返回值构成),把这些非静态
数据成员注入
到生成类型
里面.
这就是注入源码
的基本能力,弱化版
的实现.
如:
template<typename T> struct S;
constexpr auto U = define_class(^S<int>, {
nsdm_description(^int, {.name="i", .align=64}),
nsdm_description(^int, {.name="j", .align=64}),
});
// S<int> ==等价于.
// template<> struct S<int> {
// alignas(64) int i;
// alignas(64) int j;
// };
为S
自动生成的非静态数据成员
,如果不指定nsdm_options_t
,则生成的数据成员名默认为_0,_1,_2...
.
回到Tuple
的实现,传统方法一个是递归继承
,一个是递归复合
,实现后者
时有许多问题,因此一般利用前者
实现.
而利用反射
的生成代码
能力,可直接
合成一个storage
内部类,把所有Tuple
元素全部注入
到该内部类
当中,便可轻易
地生成一个Tuple
类.
借助反射,很容易实现std::tuple_element
:
template<std::size_t I, typename... Ts>
struct std::tuple_element<I, Tuple<Ts...>> {
static constexpr std::array types = {^Ts...};
using type = [: types[I] :];
};
std::get
的实现同样简单
:
consteval std::meta::info get_nth_nsdm(std::meta::info r, std::size_t n) {
return nonstatic_data_members_of(r)[n];
}
template<std::size_t I, typename... Ts>
constexpr auto get(Tuple<Ts...> &t) noexcept -> std::tuple_element_t<I, Tuple<Ts...>>& {
return t.data.[:get_nth_nsdm(^decltype(t.data), I):];
}
通过反射
,可直接操作类型元信息
,不再需要额外的奇技淫巧
去递归
取这些信息.
这也是一个生成代码
的示例:
namespace std::meta { consteval auto make_nsdm_description(info type, nsdm_options options = {}) { return nsdm_description(type, options); } } template <typename T, std::size_t N> struct struct_of_arrays_impl; consteval auto make_struct_of_arrays(std::meta::info type, std::meta::info N) -> std::meta::info { std::vector<std::meta::info> old_members = nonstatic_data_members_of(type); std::vector<std::meta::nsdm_description> new_members = {}; for (std::meta::info member : old_members) { auto array_type = substitute(^std::array, {type_of(member), N }); auto mem_descr = make_nsdm_description(array_type, {.name = name_of(member)}); new_members.push_back(mem_descr); } return std::meta::define_class( substitute(^struct_of_arrays_impl, {type, N}), new_members); } template <typename T, size_t N> using struct_of_arrays = [: make_struct_of_arrays(^T, ^N) :]; struct point { float x; float y; float z; }; int main() { using points = struct_of_arrays<point, 2>; points p = { .x={1.1, 2.2}, .y={3.3, 4.4}, .z={5.5, 6.6} }; static_assert(p.x.size() == 2); static_assert(p.y.size() == 2); static_assert(p.z.size() == 2); for (size_t i = 0; i != 2; ++i) { std::cout << "p[" << i << "] = (" << p.x[i] << ", " << p.y[i] << ", " << p.z[i] << ")\n"; } } //输出: //p[0]=(1.1,3.3,5.5) //p[1]=(2.2,4.4,6.6)
把当前结构类型
的所有非静态数据成员
取出来,再根据这些信息
重新生成数组
形式的成员.
最后生成的points
相当于:
using points = struct_of_arrays<point, 2>;
// struct points {
// std::array<float, 2> x;
// std::array<float, 2> y;
// std::array<float, 2> z;
// };
再来看一个利用反射仿Rustclap(CommandLineArgumentParser)
的实现,clap
是Rust
的命令行参数解析器
.
最终效果为:
struct Args : Clap {
Option<std::string, {.use_short=true, .use_long=true}> name;
Option<int, {.use_short=true, .use_long=true}> count = 1;
};
int main(int argc, char** argv) {
auto opts = Args{}.parse(argc, argv);
for (int i = 0; i < opts.count; ++i) { //`opts.count`的类型为`int`.
std::print("Hello {}!", opts.name); //`opts.name`类型为`std::string`
}
}
示例中定制的Args
支持两个参数,一个是name
,一个是有默认值的count
.如果编译参数
为:
./test -n WG21 -c 7
-n
就对应于name
,-c
对应于count
.则输出
结果将为:
Hello WG21!
Hello WG21!
Hello WG21!
Hello WG21!
Hello WG21!
Hello WG21!
Hello WG21!
可在Args
中定制自己的参数列表
,在Clap
中封装
了所有的解析操作
.
要实现
此效果,先要定义Flags
和Option
.
struct Flags {
bool use_short;
bool use_long;
};
template <typename T, Flags flags>
struct Option {
std::optional<T> initializer;
Option() = default;
Option(T t) : initializer(t) { }
static constexpr bool use_short = flags.use_short;
static constexpr bool use_long = flags.use_long;
};
Flags
来表示参数,比如短形式为-n
,长形式就为--name
,可根据不同
方式来解析.Option
来表示定制的可选参数,有两个
构造器,表示可选初化参数值
.
比如只写./test -n WG21
,此时count
提供默认初化为1
,从而简化参数.
接着,定义Clap
的解析方式:
struct Clap {
template <typename Spec>
auto parse(this Spec const& spec, int argc, char** argv) {
//...
}
};
这里使用了C++23
的推导本
作为定制点
的表示方式,从而简化传统的CRTP
方式.把argc
和argv
传递进来,下一步操作
:
template <typename Spec>
auto Clap::parse(this Spec const& spec, int argc, char** argv) {
std::vector<std::string_view> cmdline(argv + 1, argv + argc);
//检查`cmdline`是否包含`--help`等.
struct Opts;
static_assert(is_type(spec_to_opts(^Opts, ^Spec)));
Opts opts;
//...
如果参数列表为./test -n WG21 -c 7
,则除了第一个参数,剩余的实际参数
都保存到cmdline
中,所以cmdline
的大小为4
.
接着开始解析
,先通过生成代码
自动生成Opts
类,该类作为解析的结果
,也就是auto opts=Args{}.parse(argc,argv);
中的opts
类型.
根据用户自定义的Args
类中的非静态数据成员
自动生成该返回类型
,生成后的结构
为:
struct Opts { std::string name; int count; };
通过spec_to_opts
来生成,实现为:
consteval auto spec_to_opts(std::meta::info opts, std::meta::info spec) -> std::meta::info {
std::vector<std::meta::nsdm_description> new_members;
for (auto member : nonstatic_data_members_of(spec)) {
auto new_type = template_arguments_of(type_of(member))[0];
new_members.push_back(make_nsdm_description(new_type, {.name=name_of(member)}));
}
return define_class(opts, new_members);
}
逻辑不算复杂,就是使用前面
的nsdm_description
和define_class
来生成简单类型
的代码.
因为不支持扩展语句
,因此下一步要借助新Z类型
和expand()
来遍历
参数.
template <typename Spec> auto Clap::parse(this Spec const& spec, int argc, char** argv) { //... struct Z { std::meta::info spec; std::meta::info opt; }; [:std::meta::expand([]() consteval { auto spec_members = nonstatic_data_members_of(^Spec); auto opts_members = nonstatic_data_members_of(^Opts); std::vector<Z> v; for (size_t i = 0; i != spec_members.size(); ++i) { v.push_back({.spec=spec_members[i], .opt=opts_members[i]}); } return v; }()):] >> [&]<auto Z>{ //... }
Z
包含两个分别保存Args
和Opts
的非静态数据成员信息的成员
,当前示例
它的大小为2
.每一组信息就对应一个参数
,2
个分别对应-n
和-c
.
如果用扩展语句
写,逻辑
会更加清晰,对应写法为:
template for (constexpr auto [sm, om] : std::views::zip(nonstatic_data_members_of(^Spec),
nonstatic_data_members_of(^Opts))) {
//...
}
具体
处理每一组
参数的逻辑
如下:
template <typename Spec> auto Clap::parse(this Spec const& spec, int argc, char** argv) { //... >> [&]<auto Z>{ constexpr auto sm = Z.spec; constexpr auto om = Z.opt; auto& cur = spec.[:sm:]; //查找与此选项关联的参数 auto it = std::find_if(cmdline.begin(), cmdline.end(), [&](std::string_view arg){ return cur.use_short && arg.size() == 2 && arg[0] == '-' && arg[1] == name_of(sm)[0] || cur.use_long && arg.starts_with("--") && arg.substr(2) == name_of(sm); }); if (it == cmdline.end()) { //无此参数 if constexpr (has_template_arguments(type_of(om)) && template_of(type_of(om)) == ^std::optional) { //`类型`是可选的,所以参数也是 return; } else if (cur.initializer) { //类型不是可选的,但提供了一个可用的初化器. opts.[:om:] = *cur.initializer; return; } else { std::cerr << "Missing required option " << name_of(sm) << '\n'; std::exit(EXIT_FAILURE); } } else if (it + 1 == cmdline.end()) { std::cout << "Option " << *it << " for " << name_of(sm) << " is missing a value\n"; std::exit(EXIT_FAILURE); } //好了,找到了参数,试解析一下 auto iss = std::ispanstream(it[1]); if (iss >> opts.[:om:]; !iss) { std::cerr << "Failed to parse " << it[1] << " into option " << name_of(sm) << " of type " << name_of(type_of(om)) << '\n'; std::exit(EXIT_FAILURE); } }; return opts; }
整体实现思路
就是,在cmdline
参数列表中,根据cur
中的信息查找,如果未查到,则看参数
是否可选
,有默认可选
值的,就把该值读取出来,保存到opts
中;
如果查找
到的位置后面没有紧跟参数值
,如-n
后面什么也没有,则缺少
参数值.
如果找到了参数,则使用C++23std::ispanstream
把值读取到opts
返回值当中,it
查找到的位置为参数的位置
,参数位置
后面的it[1]
就是参数值
的位置.
如此便借助反射
实现了一个可定制的Clap
,逻辑还是比较清晰的,但受限于当前的实现,绕了一些弯路
,稍微麻烦了一些.
完整实现为:
//库 namespace clap { struct Flags { bool use_short; bool use_long; }; template <typename T, Flags flags> struct Option { std::optional<T> initializer; Option() = default; Option(T t) : initializer(t) { } static constexpr bool use_short = flags.use_short; static constexpr bool use_long = flags.use_long; }; consteval auto spec_to_opts(std::meta::info opts, std::meta::info spec) -> std::meta::info { std::vector<std::meta::nsdm_description> new_members; for (auto member : nonstatic_data_members_of(spec)) { auto new_type = template_arguments_of(type_of(member))[0]; new_members.push_back(make_nsdm_description(new_type, {.name=name_of(member)})); } return define_class(opts, new_members); } struct Clap { template <typename Spec> auto parse(this Spec const& spec, int argc, char** argv) { std::vector<std::string_view> cmdline(argv + 1, argv + argc); //检查`cmdline`是否包含`--help`等. struct Opts; static_assert(is_type(spec_to_opts(^Opts, ^Spec))); Opts opts; struct Z { std::meta::info spec; std::meta::info opt; }; [:std::meta::expand([]() consteval { auto spec_members = nonstatic_data_members_of(^Spec); auto opts_members = nonstatic_data_members_of(^Opts); std::vector<Z> v; for (size_t i = 0; i != spec_members.size(); ++i) { v.push_back({.spec=spec_members[i], .opt=opts_members[i]}); } return v; }()):] >> [&]<auto Z>{ constexpr auto sm = Z.spec; constexpr auto om = Z.opt; auto& cur = spec.[:sm:]; //查找与此选项关联的参数 auto it = std::find_if(cmdline.begin(), cmdline.end(), [&](std::string_view arg){ return cur.use_short && arg.size() == 2 && arg[0] == '-' && arg[1] == name_of(sm)[0] || cur.use_long && arg.starts_with("--") && arg.substr(2) == name_of(sm); }); if (it == cmdline.end()) { //无此参数 if constexpr (has_template_arguments(type_of(om)) && template_of(type_of(om)) == ^std::optional) { //类型是可选的,所以参数也是 return; } else if (cur.initializer) { //该类型不是可选的,但提供了一个可用初化器. opts.[:om:] = *cur.initializer; return; } else { std::cerr << "Missing required option " << name_of(sm) << '\n'; std::exit(EXIT_FAILURE); } } else if (it + 1 == cmdline.end()) { std::cout << "Option " << *it << " for " << name_of(sm) << " is missing a value\n"; std::exit(EXIT_FAILURE); } //好了,找到了参数,试解析一下 auto iss = std::ispanstream(it[1]); if (iss >> opts.[:om:]; !iss) { std::cerr << "Failed to parse " << it[1] << " into option " << name_of(sm) << " of type " << name_of(type_of(om)) << '\n'; std::exit(EXIT_FAILURE); } }; return opts; } }; }
若按100%
来谈论反射的进度,前两年更新
时进度大概在20%-30%
,而如今大概到了30%-40%
.从本文
也可见已更加完善了实现
,也全部支持最新语法
,其他相关的反射特性
也有了平替
的丐版实现
,虽说还不够简便,也缺少很多功能
,但至少能用了.
我想C++
反射也是要分几次
标准才能真正完善,进度到60%
大概可第一次
进标准,也就是进C++26
.此时会缺少注入源码
该强特性,及自定义属性
这类辅助特性
,只会包含最基本
的反射特性
.
即使如此,也敲开C++
第三阶段元编程
大门,绝对会是一个强大的C++
新纪元,产生式
元编程也会更加流行.
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。