2411C++,C++26反射示例
参考
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... } {}
};
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_field(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_field(^decltype(t.data), I):];}
//其他值类一样...
此例使用"神奇
"的std::meta::define_class
模板及nonstatic_data_members_of
,元函数成员反射
来实现
类似std::tuple
的类型
,无需在这些工具
不可用时,涉及一般复杂且贵
的模板元编程技巧
.
define_class
反射不完整的类或联
,加上非静态数据成员
描述的向量
,并完成给定
类或联类型以取得所描述的成员
.
3.9
简单变量类型
类似如何对每个带一个成员
的Ts...
,使用define_class
来实现一个元组
来动态创建
一个类型,可实现一个只定义
一个联
而不是结构
的变量.
这里的区别
是当前如何定义联的析构器
:
union U1 {int i;char c;
};
union U2 {int i;std::string s;
};
U1
有个平凡析构器
,但按已删除
(因为std::string
有个非平凡析构器
)定义U2
的析构器
.
但是,为了define_class
,这里实际上只有一个合理的待选选项
:
template <class... Ts>
union U {//所有成员Ts... members;//如果所有类型都是简单的可析构,则默认析构器constexpr ~U() requires (std::is_trivially_destructible_v<Ts> && ...) = default;//...否则,析构器闲着constexpr ~U() { }
};
如果让联
的define_class
有该行为
,则就可用比当前实现
更直接的方式,实现一个变量
.这不是std::variant
的完整实现
,但可说明该想法
:
template <typename... Ts>
class Variant {union Storage;struct Empty { };static_assert(is_type(define_class(^Storage, {data_member_spec(^Empty, {.name="empty"}),data_member_spec(^Ts)...})));static consteval std::meta::info get_nth_field(std::size_t n) {return nonstatic_data_members_of(^Storage)[n+1];}Storage storage_;int index_ = -1;//欺骗:使用`libstdc++`的实现template <typename T>static constexpr size_t accepted_index = std::__detail::__variant::__accepted_index<T, std::variant<Ts...>>;template <class F>constexpr auto with_index(F&& f) const -> decltype(auto) {return mp_with_index<sizeof...(Ts)>(index_, (F&&)f);}
public:constexpr Variant() requires std::is_default_constructible_v<Ts...[0]>//这应该有效吗:`storage_{.[:get_nth_field(0):]{}}`: storage_{.empty={}}, index_(0){std::construct_at(&storage_.[: get_nth_field(0) :]);}constexpr ~Variant() requires (std::is_trivially_destructible_v<Ts> and ...) = default;constexpr ~Variant() {if (index_ != -1) {with_index([&](auto I){std::destroy_at(&storage_.[: get_nth_field(I) :]);});}}template <typename T, size_t I = accepted_index<T&&>>requires (!std::is_base_of_v<Variant, std::decay_t<T>>)constexpr Variant(T&& t): storage_{.empty={}}, index_(-1){std::construct_at(&storage_.[: get_nth_field(I) :], (T&&)t);index_ = (int)I;}//在`P2963`前,你实际上无法很好地表达此约束constexpr Variant(Variant const&) requires (std::is_trivially_copyable_v<Ts> and ...) = default;constexpr Variant(Variant const& rhs)requires ((std::is_copy_constructible_v<Ts> and ...)and not (std::is_trivially_copyable_v<Ts> and ...)): storage_{.empty={}}, index_(-1){rhs.with_index([&](auto I){constexpr auto field = get_nth_field(I);std::construct_at(&storage_.[: field :], rhs.storage_.[: field :]);index_ = I;});}constexpr auto index() const -> int { return index_; }template <class F>constexpr auto visit(F&& f) const -> decltype(auto) {if (index_ == -1) {throw std::bad_variant_access();}return mp_with_index<sizeof...(Ts)>(index_, [&](auto I) -> decltype(auto) {return std::invoke((F&&)f, storage_.[: get_nth_field(I) :]);});}
};
实际上,如下Variant<T,U>
组成了一个存储
联类型:
union Storage {Empty empty;T unnamed0;U unnamed1;~Storage() requires std::is_trivially_destructible_v<T> && std::is_trivially_destructible_v<U> = default;~Storage() { }
}
这里问题
是,是否应该可如下用胶接器
直接初化已定义联的成员
:
: storage{.[: get_nth_field(0) :]={}}
可以说,答案应该是肯定
的,此应与其他
访问的工作方式一致
.
3.10
结构到数组的结构
#include <meta>
#include <array>
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::info> new_members = {};for (std::meta::info member : old_members) {auto type_array = substitute(^std::array, {type_of(member), N });auto mem_descr = data_member_spec(type_array, {.name = identifier_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) :];
Example:
struct point {float x;float y;float z;
};
using points = struct_of_arrays<point, 30>;
//等价于:`构 点{std::array<float,30>x;std::array<float,30>y;std::array<float,30>z;};`
同样,可很好利用nonstatic_data_members_of
和define_class
的组合.
3.11
解析命令行选项II
现在已看到了几个使用std::meta::define_class
创建类型
的示例,可创建一个更复杂
的命令行解析器示例
.
这是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`}
}
可以像这样
实现:
struct Flags {bool use_short;bool use_long;
};
template <typename T, Flags flags>
struct Option {std::optional<T> initializer = {};//一些适合`标志`的构造器和访问器
};//按`恰好`是`适当成员`的`类型`,转换`私有类型`(其所有非静态数据成员都是`选项`的).如,如果`类型`是上面介绍的`参数`的反射,则`该函`数的计算结果将是类型的反射:`struct{std::string name;int count;}`consteval auto spec_to_opts(std::meta::info opts, std::meta::info spec) -> std::meta::info {std::vector<std::meta::info> new_members;for (std::meta::info member : nonstatic_data_members_of(spec)) {auto type_new = template_arguments_of(type_of(member))[0];new_members.push_back(data_member_spec(type_new, {.name=identifier_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)//检查`命令行`是否包含`-help`等.struct Opts;static_assert(is_type(spec_to_opts(^Opts, ^Spec)));Opts opts;template for (constexpr auto [sm, om] : std::views::zip(nonstatic_data_members_of(^Spec), nonstatic_data_members_of(^Opts))) {auto const& cur = spec.[:sm:];constexpr auto type = type_of(om);//找到与`此选项`关联的参数auto it = std::ranges::find_if(cmdline,[&](std::string_view arg){return (cur.use_short && arg.size() == 2 && arg[0] == '-' && arg[1] == identifier_of(sm)[0])|| (cur.use_long && arg.starts_with("--") && arg.substr(2) == identifier_of(sm));});//无此参数if (it == cmdline.end()) {if constexpr (has_template_arguments(type) and template_of(type) == ^std::optional) {//`类型`是可选的,因此参数也是continue;} else if (cur.initializer) {//该类型不是可选的,但提供了初化器,请使用该opts.[:om:] = *cur.initializer;continue;} else {std::print(stderr, "Missing required option {}\n", display_string_of(sm));std::exit(EXIT_FAILURE);}} else if (it + 1 == cmdline.end()) {std::print(stderr, "Option {} for {} is missing a value\n", *it, display_string_of(sm));std::exit(EXIT_FAILURE);}//找到的参数,试解析它auto iss = ispanstream(it[1]);if (iss >> opts.[:om:]; !iss) {std::print(stderr, "Failed to parse {:?} into option {} of type {}\n",it[1], display_string_of(sm), display_string_of(type));std::exit(EXIT_FAILURE);}}return opts;}
};
3.12
一个通用的格式化器
struct universal_formatter {constexpr auto parse(auto& ctx) { return ctx.begin(); }template <typename T>auto format(T const& t, auto& ctx) const {auto out = std::format_to(ctx.out(), "{}{{", has_identifier(^T) ? identifier_of(^T) : "(unnamedtype)";);auto delim = [first=true]() mutable {if (!first) {*out++ = ',';*out++ = ' ';}first = false;};template for (constexpr auto base : bases_of(^T)) {delim();out = std::format_to(out, "{}", (typename [: type_of(base) :] const&)(t));}template for (constexpr auto mem : nonstatic_data_members_of(^T)) {delim();std::string_view mem_label = has_identifier(mem) ? identifier_of(mem) : "(unnamedmember)";out = std::format_to(out, ".{}={}", mem_label, t.[:mem:]);}*out++ = '}';return out;}
};
struct B { int m0 = 0; };
struct X { int m1 = 1; };
struct Y { int m2 = 2; };
class Z : public X, private Y { int m3 = 3; int m4 = 4; };
template <> struct std::formatter<B> : universal_formatter { };
template <> struct std::formatter<X> : universal_formatter { };
template <> struct std::formatter<Y> : universal_formatter { };
template <> struct std::formatter<Z> : universal_formatter { };
int main() {std::println("{}", Z());//`Z{X{B{.m0=0},.m1=1},Y{{.m0=0},.m2=2},.m3=3,.m4=4}`
}
注意,当前不能用t.[:base:]
语法访问基类子对象
,即只能使用转换
来取基类
:
static_cast<[: type_of(base) const& :]>(t), or
(typename [: type_of(base) :] const&)t
两者都必须在转换
中显式
指定类型的常性
.static_cast
还必须检查权限.
3.13
实现成员级hash_append
template <typename H, typename T> requires std::is_standard_layout_v<T>
void hash_append(H& algo, T const& t) {template for (constexpr auto mem : nonstatic_data_members_of(^T)) {hash_append(algo, t.[:mem:]);}
}
3.14
按元组转换结构
template <typename T>
constexpr auto struct_to_tuple(T const& t) {constexpr auto members = nonstatic_data_members_of(^T);constexpr auto indices = []{std::array<int, members.size()> indices;std::ranges::iota(indices, 0);return indices;}();constexpr auto [...Is] = indices;return std::make_tuple(t.[: members[Is] :]...);
}
或:
consteval auto type_struct_to_tuple(info type) -> info {return substitute(^std::tuple,nonstatic_data_members_of(type)| std::views::transform(std::meta::type_of)| std::views::transform(std::meta::type_remove_cvref)| std::ranges::to<std::vector>());
}
template <typename To, typename From, std::meta::info ... members>
constexpr auto struct_to_tuple_helper(From const& from) -> To {return To(from.[:members:]...);
}
template<typename From>
consteval auto get_struct_to_tuple_helper() {using To = [: type_struct_to_tuple(^From): ];std::vector args = {^To, ^From};for (auto mem : nonstatic_data_members_of(^From)) {args.push_back(reflect_value(mem));}/*或,使用区间:args.append_range(nonstatic_data_members_of(^From)| std::views::transform(std::meta::reflect_value));*/return extract<To(*)(From const&)>(substitute(^struct_to_tuple_helper, args));
}
template <typename From>
constexpr auto struct_to_tuple(From const& from) {return get_struct_to_tuple_helper<From>()(from);
}
在此,type_struct_to_tuple
带类似struct{T t;U const &u;V v;}
,并返回std::tuple<T,U,V>
的反射
类型.这给了中
类型.
然后,struct_to_tuple_helper
是实际转换
的函数模板
,可按非类型模板参数包
来实现成员的所有反射
.
这是个常式
函数,而不是常值
函数,因为一般,转换是运行时操作
.
但是,确定需要struct_to_tuple_helper
实例是个编译时操作
,且必须使用常值
函数处理(因为该函数调用nonstatic_data_members_of
),因此需要单独的get_struct_to_tuple_helper()
函数模板.
用替代
把所有内容
放在一起,创建需要的struct_to_tuple_helper
的实例化,并用提取
取得该实例
的编译时引用
.
因此可简单调用
的f
是指向struct_to_tuple_helper
的正确私有的函数引用
.
3.15
实现tuple_cat
template<std::pair<std::size_t, std::size_t>... indices>
struct Indexer {template<typename Tuples>//可用元组索引而不是元组的元组auto operator()(Tuples&& tuples) const {using ResultType = std::tuple<std::tuple_element_t<indices.second,std::remove_cvref_t<std::tuple_element_t<indices.first, std::remove_cvref_t<Tuples>>>>...>;return ResultType(std::get<indices.second>(std::get<indices.first>(std::forward<Tuples>(tuples)))...);}
};
template <class T>
consteval auto subst_by_value(std::meta::info tmpl, std::vector<T> args)-> std::meta::info
{std::vector<std::meta::info> a2;for (T x : args) {a2.push_back(std::meta::reflect_value(x));}return substitute(tmpl, a2);
}
consteval auto make_indexer(std::vector<std::size_t> sizes)-> std::meta::info
{std::vector<std::pair<int, int>> args;for (std::size_t tidx = 0; tidx < sizes.size(); ++tidx) {for (std::size_t eidx = 0; eidx < sizes[tidx]; ++eidx) {args.push_back({tidx, eidx});}}return subst_by_value(^Indexer, args);
}
template<typename... Tuples>
auto my_tuple_cat(Tuples&&... tuples) {constexpr typename [: make_indexer({type_tuple_size(type_remove_cvref(^Tuples))...}) :] indexer;return indexer(std::forward_as_tuple(std::forward<Tuples>(tuples)...));
}
3.16
命名元组
实现命名元组
的难点,实际上是按非类型模板参数
对待串.因为不能只传递"x"
给auto V
形式的非类型模板参数
,所以有两个方法
来指定组成部分
:
可引入双
类型,这样就可以写make_named_tuple<pair<int,"x">,pair<double,"y">>()
或
可一直反射
,这样就可以写:
make_named_tuple<^int, std::meta::reflect_value("x"), ^double, std::meta::reflect_value("y")>()
当前不支持拼接串字面
,且给定合适的fixed_string
类型,双
方法遵守define_class
中已显示的类似模式
:
template <class T, fixed_string Name>
struct pair {static constexpr auto name() -> std::string_view { return Name.view(); }using type = T;
};
template <class... Tags>
consteval auto make_named_tuple(std::meta::info type, Tags... tags) {std::vector<std::meta::info> nsdms;auto f = [&]<class Tag>(Tag tag){nsdms.push_back(data_member_spec(dealias(^typename Tag::type),{.name=Tag::name()}));};(f(tags), ...);return define_class(type, nsdms);
}
struct R;
static_assert(is_type(make_named_tuple(^R, pair<int, "x">{}, pair<double, "y">{})));
static_assert(type_of(nonstatic_data_members_of(^R)[0]) == ^int);
static_assert(type_of(nonstatic_data_members_of(^R)[1]) == ^double);
int main() {[[maybe_unused]] auto r = R{.x=1, .y=2.0};
}
或,可在值
域中保存所有内容
,来避免非类型模板参数
的问题:
consteval auto make_named_tuple(std::meta::info type, std::initializer_list<std::pair<std::meta::info, std::string_view>> members) {std::vector<std::meta::data_member_spec> nsdms;for (auto [type, name] : members) {nsdms.push_back(data_member_spec(type, {.name=name}));}return define_class(type, nsdms);
}
struct R;
static_assert(is_type(make_named_tuple(^R, {{^int, "x"}, {^double, "y"}})));
static_assert(type_of(nonstatic_data_members_of(^R)[0]) == ^int);
static_assert(type_of(nonstatic_data_members_of(^R)[1]) == ^double);
int main() {[[maybe_unused]] auto r = R{.x=1, .y=2.0};
}
3.17
编译时票据计数器
此处建议的特征使得在编译时
更新票证计数器
更容易.这不是理想
的实现(更喜欢直接支持编译时
,即常值
,变量),但它展示了编译时
如何搞出可变状态
.
class TU_Ticket {template<int N> struct Helper;
public:static consteval int next() {int k = 0;//搜索下个不完整`'Helper<k>"`.std::meta::info r;while (is_complete_type(r = substitute(^Helper, { std::meta::reflect_value(k) })))++k;//定义`'Helper<k>'`并返回其索引.define_class(r, {});return k;}
};
constexpr int x = TU_Ticket::next();
static_assert(x == 0);
constexpr int y = TU_Ticket::next();
static_assert(y == 1);
constexpr int z = TU_Ticket::next();
static_assert(z == 2);
3.18
模拟反射类型
尽管认为单个不透明的std::meta::info
类型是最好
的,且对反射最具可扩展性
,但承认SG7
表达了对未来支持"类型反射
"的愿望.
下面演示了一种由不同类型
表示不同类的反射
的组装类型反射库
的可能方法,及此处建议的工具
.
//表示判定限制的`'std::meta::info'`.
template <std::meta::info Pred>requires (std::predicate<[:type_of(Pred):], std::meta::info>)
struct metatype {std::meta::info value;//除非满足`判定`,否则`构造`的格式是错误的.consteval metatype(std::meta::info r) : value(r) {if ()throw "Reflection is not a member of this metatype";}//转为`'std::meta::info'`允许拼接此类型的值.consteval operator std::meta::info() const { return value; }static consteval bool check(std::meta::info r) { return [:Pred:](r); }
};//表示"匹配失败"已知元类型的类型.
struct unmatched {consteval unmatched(std::meta::info) {}static consteval bool check(std::meta::info) { return true; }
};
//用`更具描述性的类型`,返回给定`"更富有"`反射.
template <typename... Choices>
consteval std::meta::info enrich(std::meta::info r) {//因为控制类型,所以知道第一个构造器是取`信息`的构造器.在}处添加了复制/移动构造器,因此是列表中的最后构造器. std::array ctors = {*(members_of(^Choices) | std::views::filter(std::meta::is_constructor)).begin()...,*(members_of(^unmatched) | std::views::filter(std::meta::is_constructor)).begin()};std::array checks = {^Choices::check..., ^unmatched::check};for (auto [check, ctor] : std::views::zip(checks, ctors))if (extract<bool>(reflect_invoke(check, {reflect_value(r)})))return reflect_invoke(ctor, {reflect_value(r)});std::unreachable();
}
可利用此机制
,根据按参数
提供的反射
"类型"来选择不同重载函数
.
using type_t = metatype<^std::meta::is_type>;
using template_t = metatype<^std::meta::is_template>;
//对不同反射"类型",重载函数的示例.
void PrintKind(type_t) { std::println("type"); }
void PrintKind(template_t) { std::println("template"); }
void PrintKind(unmatched) { std::println("unknown kind"); }
int main() {//按以下值之一分类反射:`Type,Function`或`Unmatched`.auto enrich = [](std::meta::info r) { return ::enrich<type_t, template_t>(r); };//演示如何使用`'变富'`来选择重载.PrintKind([:enrich(^metatype):]); //`"template"`PrintKind([:enrich(^type_t):]); //`"type"`PrintKind([:enrich(std::meta::reflect_value(3):]); //`"unknownkind"`
}
注意,可按包装字面
类型的值
,或包装可能不同类型
的多个值
泛化元型
类.
如,这可用来根据以下因子
选择编译时重载
:两个整数
是否共享相同奇偶校验
,可选
中是否有值
,变量(variant)
或任意(any)
持有的值的类型
,或编译时
串的语法形式
.
在C++23
中以相同泛型
实现相同目标
,需要两次拼写参数
:第一次
取得模板参数
的"分类标签
",然后再次调用函数
,即
Printer::PrintKind<classify(^int)>(^int).
//或更糟......
fn<classify(Arg1, Arg2, Arg3)>(Arg1, Arg2, Arg3).
相关文章:
2411C++,C++26反射示例
参考 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...>…...

Ubuntu上搭建Flink Standalone集群
Ubuntu上搭建Flink Standalone集群 本文部分内容转自如下链接。 环境说明 ubuntu 22.06 先执行apt-get update更新环境 第1步 安装JDK 通过apt自动拉取 openjdk8 apt-get install openjdk-8-jdk执行java -version,如果能显示Java版本号,表示安装并…...
C语言 精选真题2
题目要求:将形参s所指向的字符串转换为整数并且返回 知识点: 将字符1转化为整数1 int fun(char *s) {int flag1,n0; if(*s-) //先根据第一个符号来判断是正负;然后读取第二位{flag-1;s; }else if(*s){s;}while(*s>0&&…...
Netty篇(WebSocket)
目录 一、简介 二、特点 三、websock应用场景 四、websocket案例 1. 服务端 2. 处理器 3. 页面端处理 五、参考文献 一、简介 没有其他技术能够像WebSocket一样提供真正的双向通信,许多web开发者仍然是依赖于ajax的长轮询来 实现。(注ÿ…...

云原生-docker安装与基础操作
一、云原生 Docker 介绍 Docker 在云原生中的优势 二、docker的安装 三、docker的基础命令 1. docker pull(拉取镜像) 2. docker images(查看本地镜像) 3. docker run(创建并启动容器) 4. docker ps…...

MySQL数据库:SQL语言入门 【上】(学习笔记)
SQL(Structured Query Language)是结构化查询语言的简称,它是一种数据库查询和程序设计语言,同时也是目前使用最广泛的关系型数据库操作语言。(95%适用于所有关系型数据库) 【 SQL是关系型数据库通用的操作…...

重学 Android 自定义 View 系列(六):环形进度条
目标 自定义一个环形进度条,可以自定义其最大值、当前进度、背景色、进度色,宽度等信息。 最终效果如下(GIF展示纯色有点问题): 1. 结构分析 背景圆环:表示进度条的背景。进度圆环:表示当前…...

nodejs 020: React语法规则 props和state
props和state 在 React 中,props 和 state 是管理数据流的两种核心机制。理解它们之间的区别和用途是构建 React 应用程序的基础。 一、props 和 state的区别 特性propsstate定义方式由父组件传递给子组件的数据组件内部管理的本地数据是否可修改不可变ÿ…...

STM32问题集
这里写目录标题 一、烧录1、 Can not connect to target!【ST-LINK烧录】 一、烧录 1、 Can not connect to target!【ST-LINK烧录】 烧录突然 If the target is in low power mode, please enable “Debug in Low Power mode” option from Target->settings menu 然后就&…...

SwiftUI(十二)- 容器组件 布局与结构的基石
引言 在用户界面开发中,布局是设计一个应用程序的视觉层次和交互体验的核心之一。无论是设计简单的按钮排布,还是复杂的多层次页面,合理的布局和结构可以极大地提升用户体验。而容器组件,作为将多个视图整合、组织、排列的工具&a…...

想租用显卡训练自己的网络?AutoDL保姆级使用教程(PyCharm版)
各位小伙伴们大家好~ 不知道各位同学在科研过程中是否有这样的苦恼 电脑无显卡。难不成我要用CPU跑实验吗?救救我吧电脑显卡算力太低。训练过程慢慢慢慢慢,等半天都出不来结果电脑显卡显存不够,batchsize稍微高一点点,就要爆显存…...
LeetCode【0039】组合总和
本文目录 1 中文题目2 求解方法:回溯法2.1 方法思路2.2 Python代码2.3 复杂度分析 3 题目总结 1 中文题目 给定一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 &#…...

AscendC从入门到精通系列(一)初步感知AscendC
1 什么是AscendC Ascend C是CANN针对算子开发场景推出的编程语言,原生支持C和C标准规范,兼具开发效率和运行性能。基于Ascend C编写的算子程序,通过编译器编译和运行时调度,运行在昇腾AI处理器上。使用Ascend C,开发者…...
PostgreSQL中的COPY命令:高效数据导入与导出
在PostgreSQL数据库中,数据导入和导出是日常工作中常见的操作。传统的插入(INSERT)方法虽然可以实现数据的导入,但在处理大量数据时效率较低。而COPY命令则提供了一个快速、高效的方式来完成这一任务。COPY命令不仅可以用于将数据…...

【HAL库】STM32F105VCTx多通道ADC+DMA方式的【STM32CubeMX】配置及代码实现
相关代码编写 配置好后点击生成代码,在生成代码的adc.c文件中的初始化函数MX_ADC1_Init中添加如下代码: HAL_ADCEx_Calibration_Start(&hadc1); /* 校准ADC */HAL_ADC_Start_DMA(&hadc1,(uint32_t*)ADC_Value,ADC_DMA_…...

[SaaS] 数禾科技 AIGC生成营销素材
https://zhuanlan.zhihu.com/p/923637935https://zhuanlan.zhihu.com/p/923637935...
vue3中查找字典列表中某个元素的值对应的列表索引值
vue3中查找字典列表中某个元素的值对应的列表索引值 目录思路方法代码实现示例解释说明 目录 思路方法 要获取字典列表中某个元素的值对应的列表索引值,可以使用数组的 findIndex 方法。这个方法返回数组中满足提供的测试函数的第一个元素的索引。如果没有找到&am…...
爱普生机器人EPSON RC
爱普生机器人Epson RC系列,搭配其专用的Epson RC编程语言和软件环境,为用户提供了一个直观且功能强大的机器人控制和编程解决方案。以下是对Epson RC及爱普生机器人的一些详细介绍: Epson RC 定义:Epson RC 是爱普生机器人技术中…...

Linux探秘坊-------1.系统核心的低语:基础指令的奥秘解析(1)
1.Linux的背景介绍 Linux 操作系统的发展历程充满了激情与创新喵~🎀 萌芽期 (1983 - 1991):Linux 的历史可追溯到 1983 年,理查德斯托曼 (Richard Stallman) 发起 GNU 计划,目标是创建一个自由软件操作系统。1987 年发…...

❤React-JSX语法认识和使用
1、JSX基本使用 JSX是React的核心 JSX是ES的扩展 jsx语法 -> 普通的JavaScript代码 -> babel React可以使用JSX的前提和原因: React生态系统支持: 脚手架通常用于构建React应用程序,而JSX是React框架的核心语法之一。因此…...
设计模式和设计原则回顾
设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...
反向工程与模型迁移:打造未来商品详情API的可持续创新体系
在电商行业蓬勃发展的当下,商品详情API作为连接电商平台与开发者、商家及用户的关键纽带,其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息(如名称、价格、库存等)的获取与展示,已难以满足市场对个性化、智能…...

8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂
蛋白质结合剂(如抗体、抑制肽)在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上,高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术,但这类方法普遍面临资源消耗巨大、研发周期冗长…...
深入浅出:JavaScript 中的 `window.crypto.getRandomValues()` 方法
深入浅出:JavaScript 中的 window.crypto.getRandomValues() 方法 在现代 Web 开发中,随机数的生成看似简单,却隐藏着许多玄机。无论是生成密码、加密密钥,还是创建安全令牌,随机数的质量直接关系到系统的安全性。Jav…...

ETLCloud可能遇到的问题有哪些?常见坑位解析
数据集成平台ETLCloud,主要用于支持数据的抽取(Extract)、转换(Transform)和加载(Load)过程。提供了一个简洁直观的界面,以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...

在WSL2的Ubuntu镜像中安装Docker
Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包: for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)
目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关࿰…...

学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2
每日一言 今天的每一份坚持,都是在为未来积攒底气。 案例:OLED显示一个A 这边观察到一个点,怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 : 如果代码里信号切换太快(比如 SDA 刚变,SCL 立刻变&#…...

pikachu靶场通关笔记19 SQL注入02-字符型注入(GET)
目录 一、SQL注入 二、字符型SQL注入 三、字符型注入与数字型注入 四、源码分析 五、渗透实战 1、渗透准备 2、SQL注入探测 (1)输入单引号 (2)万能注入语句 3、获取回显列orderby 4、获取数据库名database 5、获取表名…...
HTML前端开发:JavaScript 获取元素方法详解
作为前端开发者,高效获取 DOM 元素是必备技能。以下是 JS 中核心的获取元素方法,分为两大系列: 一、getElementBy... 系列 传统方法,直接通过 DOM 接口访问,返回动态集合(元素变化会实时更新)。…...