参考
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::enumerators_of(^E)) {
if (value == [:e:]) {
return std::string(std::meta::identifier_of(e));
}
}
return "<unnamed>";
}
//使用`扩展`解决方法
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::identifier_of(e);
}
};
return result;
}
示例
3.1
来回
第一例
并不引人注目
,而是要展示
如何在反射域和语法域
间来回切换
:
constexpr auto r = ^int;
typename[:r:] x = 42; //等价于:`int x=42;`
typename[:^char:] c = '*'; //等价于:`charc='*';`
在与依赖全名
相同环境中,即,在标准
叫做仅类型的环境
中,可省略型名
前缀.如:
using MyType = [:sizeof(int)<sizeof(long)? ^long : ^int:]; //隐式`"型名"`前缀.
3.2
选择成员
第二个示例
允许为特定类型
"按序号
"选择成员
:
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))`不是一个常数.
}
此例
还说明了可以访问位
字段.
注意,像s.[:member_number(1):]
此"访问成员拼接
"是比传统语法
更直接的访问成员机制
.它不涉及查找成员名,检查访问
,或如果拼接反射值
表示成员函数
的解析重载
.
该提案包括许多常值
"元函数
",可用它们内省各种语言
结构.这些元函数
包括,描述给定类型
的非静态成员
返回一个反射值向量
的std::meta::nonstatic_data_members_of
.
因此,可重写
上例为:
struct S { unsigned i:2, j:6; };
consteval auto member_number(int n) {
return std::meta::nonstatic_data_members_of(^S)[n];
}
int main() {
S s{0, 0};
s.[:member_number(1):] = 42; //等价于:`s.j=42;`
s.[:member_number(5):] = 0; //错误`(member_number(5))`不是一个常数.
}
此提案指定std::meta
名字空间与(std::meta::info
)反射类型
关联;因此,在上例
中可省略std::meta::
限定.
另一个经常有用
的元函数
是返回一个,描述声明给定反射值
表示的实例的std::string_view
标识的std::meta::identifier_of
.
有了此工具
,可按"串"
访问非静态数据成员
:
struct S { unsigned i:2, j:6; };
consteval auto member_named(std::string_view name) {
for (std::meta::info field : nonstatic_data_members_of(^S)) {
if (has_identifier(field) && identifier_of(field) == name)
return field;
}
}
int main() {
S s{0, 0};
s.[:member_named("j"):] = 42; //等价于:`s.j=42;`
s.[:member_named("x"):] = 0; //错误`(member_named("x")`不是一个常数.
}
3.3
类型列表到大小列表
在此,大小
是一个用{sizeof(int),sizeof(float),sizeof(double)}
初化的std::array<std::size_t,3>
:
constexpr std::array types = {^int, ^float, ^double};
constexpr std::array sizes = []{
std::array<std::size_t, types.size()> r;
std::views::transform(types, r.begin(), std::meta::size_of);
return r;
}();
比较此方法
与以下基于类型
的生成相同数组大小
的方法:
template<class...> struct list {};
using types = list<int, float, double>;
constexpr auto sizes = []<template<class...> class L, class... T>(L<T...>) {
return std::array<std::size_t, sizeof...(T)>{{ sizeof(T)... }};
}(types{});
3.4
实现make_integer_sequence
与使用平凡模板元编程
的手动方法
相比,尽管今天的标准库依赖内部函数
,可提供更好
的make_integer_sequence
实现:
#include <utility>
#include <vector>
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):];
注意,替换模板
过程中隐式
的缓存
仍适用.因此,多次使用make_integer_sequence<int,20>
计算只涉及一次make_integer_seq_refl<int>(20)
.
3.5
取类布局
struct member_descriptor
{
std::size_t offset;
std::size_t size;
};
//返回`std::array<member_descriptor,N>`
template <typename S>
consteval auto get_layout() {
constexpr auto members = nonstatic_data_members_of(^S);
std::array<member_descriptor, members.size()> layout;
for (int i = 0; i < members.size(); ++i) {
layout[i] = {.offset=offset_of(members[i]).bytes, .size=size_of(members[i])};
}
return layout;
}
struct X
{
char a;
int b;
double c;
};
/*`常式`*/ auto Xd = get_layout<X>();
/*其中`Xd`将是`std::array<member_descriptor,3>{{{0,1},{4,4},{8,8}}}`*/
3.6
枚举转串
最常见
的工具之一
是按串转换枚举值
,此例依赖扩展语句
:
template <typename E>
requires std::is_enum_v<E>
constexpr std::string enum_to_string(E value) {
template for (constexpr auto e : std::meta::enumerators_of(^E)) {
if (value == [:e:]) {
return std::string(std::meta::identifier_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::enumerators_of(^E)) {
if (name == std::meta::identifier_of(e)) {
return [:e:];
}
}
return std::nullopt;
}
但是不必使用扩展语句
,也可用算法
.如,enum_to_string
也可这样实现,此例依赖
非瞬态常式
分配,这也演示了根据枚举器
个数选择不同算法
:
template <typename E>
requires std::is_enum_v<E>
constexpr std::string enum_to_string(E value) {
constexpr auto get_pairs = []{
return std::meta::enumerators_of(^E)
| std::views::transform([](std::meta::info e){
return std::pair<E, std::string>(std::meta::extract<E>(e), std::meta::identifier_of(e));
})
};
constexpr auto get_name = [](E value) -> std::optional<std::string> {
if constexpr (enumerators_of(^E).size() <= 7) {
//如果枚举器不多,请使用`find_if()`的向量
constexpr auto enumerators = get_pairs() | std::ranges::to<std::vector>();
auto it = std::ranges::find_if(enumerators, [value](auto const& pr){
return pr.first == value;
};
if (it == enumerators.end()) {
return std::nullopt;
} else {
return it->second;
}
} else {
//如果有很多枚举器,请使用`find()`的`映射`
constexpr auto enumerators = get_pairs() | std::ranges::to<std::map>();
auto it = enumerators.find(value);
if (it == enumerators.end()) {
return std::nullopt;
} else {
return it->second;
}
}
};
return get_name(value).value_or("<unnamed>");
}
在编译时,可根据enumerators_of
的长度
选择更复杂
的查找算法
(^E
)
可生成紧凑
的双向持久数据结构
,以最小的消费空间
同时支持enum_to_string
和string_to_enum
等.
3.7
解析命令行选项
下一例
展示了命令行选项解析器
,如何根据成员名
自动推导标志
来工作.真正的命令行解析器
当然会更复杂
,这仅是个开始
.
template<typename Opts>
auto parse_options(std::span<std::string_view const> args) -> Opts {
Opts opts;
template for (constexpr auto dm : nonstatic_data_members_of(^Opts)) {
auto it = std::ranges::find_if(args,
[](std::string_view arg){
return arg.starts_with("--") && arg.substr(2) == identifier_of(dm);
});
if (it == args.end()) {
//未提供选项,请使用`默认`
continue;
} else if (it + 1 == args.end()) {
std::print(stderr, "Option {} is missing a value\n", *it);
std::exit(EXIT_FAILURE);
}
using T = typename[:type_of(dm):];
auto iss = std::ispanstream(it[1]);
if (iss >> opts.[:dm:]; !iss) {
std::print(stderr, "Failed to parse option {} into a {}\n", *it, display_string_of(^T));
std::exit(EXIT_FAILURE);
}
}
return opts;
}
struct MyOpts {
std::string file_name = "input.txt"; //`"-file_name<string>"`选项
int count = 1; //`"-count<int>"`选项
};
int main(int argc, char *argv[]) {
MyOpts opts = parse_options<MyOpts>(std::vector<std::string_view>(argv+1, argv+argc));
}
3.8
简单元组类型
#include <meta>
template<typename... Ts> struct Tuple {
struct storage;
static_assert(is_type(define_class(^storage, {data_member_spec(^Ts)...})));
storage data;
Tuple(): data{} {}
Tuple(Ts const& ...vs): data{ vs... }