- 主题:g++16.1已经支持反射了,于是我试了试
虽然还没完全实现,但已经基本可用,环境Win11 MSYS2 UCRT63+1 g++ v16.1.0
print_fields函数可接收各种不同结构体对象做参数,能在编译时确定的就行。

--
FROM 171.213.178.*
另外,std::index_sequence<Is ...>可以用g++ 16.1已经支持的template for来替代了,可读性要好些,我以前在编程技术版发的那个可变参数模板的匿名函数迭代也可以改成template for循环。
@buildtolast 当时不写循环还真不是我炫技,那时候运行时循环变量不能作为下标去索引 constexpr 数组(除非编译器支持template for 或者 consteval for,当前尚未标准化)。
因此必须通过编译期索引序列展开(index_sequence + 折叠表达式)来实现“遍历”。
template<typename T>
void print_fields_by_template_for(const T& obj) {
constexpr auto N = std::meta::nonstatic_data_members_of(
^^T, std::meta::access_context::unchecked()
).size();
std::println("Processing object of type: {} ({} fields)",
std::meta::display_string_of(^^T), N);
constexpr auto members =
std::define_static_array(
nonstatic_data_members_of(
^^T, std::meta::access_context::unchecked()
)
);
template for (constexpr std::size_t i : std::views::iota(0uz, N)) {
constexpr auto member = members[i];
constexpr auto name = std::meta::identifier_of(member);
auto const& value = obj.[: member :]; // 拼接成员访问
print_value(name, value, i);
}
}
--
FROM 171.213.178.*
#include <iostream>
#include <experimental/meta> // C++26 反射头文件(具体命名空间可能依编译器实现而定)
#include <utility>
// 通用反射打印(支持任意字段数)
template<typename T>
void print_fields(const T& obj) {
// 1. 获取 T 的所有非静态数据成员的数量 N
constexpr auto N = std::meta::nonstatic_data_members_of(
^^T,
std::meta::access_context::unchecked()
).size();
// 2. 打印类型名称和字段数量
std::println(
"Processing object of type: {} ({} fields)",
std::meta::display_string_of(^^T),
N
);
// 3. 定义一个泛型 Lambda(接受编译期模板参数包 Is)
// 并通过传入 std::make_index_sequence<N>{} 立即调用它
[&]<std::size_t... Is>(std::index_sequence<Is...>) {
// 4. C++17 折叠表达式(Fold Expression)结合 逗号运算符
// 它会将包中的每一个索引 Is 展开为独立的执行语句
(
( // 内部小括号 bbb:包裹着一个“定义并立即执行”的内部 Lambda
[&] {
// a. 获取第 Is 个数据成员的反射元数据(也是一个反射对象)
constexpr auto member = std::meta::nonstatic_data_members_of(
^^T,
std::meta::access_context::unchecked()
)[Is];
// b. 获取该成员的标识符名称(例如 "age", "name")
constexpr auto name = std::meta::identifier_of(member);
// c. 使用语法糖构件(Splice)来实际访问 obj 对象的该成员
auto& value = obj.[:member:];
// d. 调用具体的打印函数
print_value(name, value, Is);
}() // 这里的 () 表示立即执行这个内部 Lambda
), // 内部小括号 bbb 结束
... // 逗号折叠表达式的展开符:将以上的行为对 Is... 中的每个元素依次重复
); // 外部小括号 aaa 结束
}(std::make_index_sequence<N>{}); // 传入生成的 0 到 N-1 序列并触发外部 Lambda
}
--
FROM 113.132.10.*
(
( [&]{ ... }() ),
...
);
这是一种 C++17 逗号折叠表达式(Unary Right Fold over Comma)。其标准语法形式为:( Expression , ... )。
当 Is... 为 0, 1, 2 时,外层的 小括号aaa 包裹的代码会被逗号运算符展开成这样:
(
( [&]{ /* 处理 Is = 0 */ }() ),
( [&]{ /* 处理 Is = 1 */ }() ),
( [&]{ /* 处理 Is = 2 */ }() )
);
--
FROM 113.132.10.*
为什么里面还要嵌套一个“小括号bbb”,来包围折叠的表达式(内部lambda2)?
为了每一轮都能定义新的 constexpr 局部变量:
在折叠表达式内部,如果你直接写一堆代码,它们在展开后会处于同一个作用域内。如果你直接连续写:
constexpr auto member = ...[0]; constexpr auto member = ...[1];
会导致变量名冲突(Redefinition)。
通过内部 Lambda 创造独立作用域:
小括号bbb 内部包裹着 [&]{ ... }(),这是一个闭包并立即执行。每一次折叠展开,都会开辟一个独立的 Lambda 局部作用域。
在 Is=0 的作用域里,member 属于 Is=0;
在 Is=1 的作用域里,member 属于 Is=1;
它们互不干扰,完美解决了编译期命名冲突和按顺序执行的问题。
【 在 DoorWay 的大作中提到: 】
: (
: ( [&]{ ... }() ),
: ...
: ...................
--
FROM 113.132.10.*
逗号表达式, C时代的 (exp1, exp2,exp3,exp4),依次执行,然后返回最后一个
一元右折叠表达式(C++17 引入)
利用参数包(Parameter Pack)的展开机制,在每个复制出的表达式之间用“逗号”连接起来。
语法: ( pack , ... )
展开规则: 如果你的参数包 Is... 是 0, 1, 2,那么
( 表达式(Is) , ... )
展开后就变成了标准的逗号表达式:(exp0,exp1,exp2)
发展逻辑:C++17 发现大家经常需要对参数包里的每个元素做同样的事情,于是借用了 C 时代的逗号表达式,发明了逗号折叠表达式,用来批量生成按顺序执行的代码。
【 在 DoorWay 的大作中提到: 】
: 为什么里面还要嵌套一个“小括号bbb”,来包围折叠的表达式(内部lambda2)?
: 为了每一轮都能定义新的 constexpr 局部变量:
: 在折叠表达式内部,如果你直接写一堆代码,它们在展开后会处于同一个作用域内。如果你直接连续写:
: ...................
--
FROM 113.132.10.*
C++26 提案中推进的编译期 for 循环(template for)。
折叠表达式,可以被简化为下面这样:
template<typename T>
void print_fields(const T& obj) {
constexpr auto members = std::meta::nonstatic_data_members_of(
^^T, std::meta::access_context::unchecked()
);
// C++26 语法:直接在编译期展开循环!
// 自动为每一个成员生成独立的作用域,再也不需要 index_sequence 和折叠表达式了
template for (constexpr auto member : members) {
constexpr auto name = std::meta::identifier_of(member);
auto& value = obj.[:member:];
// 假设我们有一个简单的计数器或直接打印
print_value(name, value);
}
}
--
FROM 113.132.10.*
完全理解。我以前实现过一版,需要用户注册成员变量,形成一个tuple. 然后遍历tuple.
后面的print代码就一样了: make_index_sequence+折叠表达式展开。
当时最难理解的,就是这个 (FoldExp, ...) 了。因为日常非元编程,这个点是我有意排除,决定不用。
没有template for 的折叠表达式展开(借助逗号表达式),给人的感觉是一桌好菜好酒,但不给好用的筷子,吃得时候得自己现做一双,或者下单等外卖来送筷子。筷子成了重点。
那一版另一个难的,就是定义成员变量类型,typedef Class::*Ptr,我也忘了语法。看到现在是 [: xxx :] ,挺好。
【 在 poocp 的大作中提到: 】
: 另外,std::index_sequence<Is ...>可以用g++ 16.1已经支持的template for来替代了,可读性要好些,我以前在编程技术版发的那个可变参数模板的匿名函数迭代也可以改成template for循环。
: @buildtolast 当时不写循环还真不是我炫技,那时候运行时循环变量不能作为下标去索引 constexpr 数组(除非编译器支持template for 或者 consteval for,当前尚未标准化)。
: 因此必须通过编译期索引序列展开(index_sequence + 折叠表达式)来实现“遍历”。
: ...................
--
修改:DoorWay FROM 113.132.10.*
FROM 113.132.10.*
多新的手,也知道并喜欢c++11的ranged for.
for 表达式真挺重要。
有了template for,反射推广能容易100倍。
--
修改:DoorWay FROM 113.132.10.*
FROM 113.132.10.*
到template for这个程度,C++终于开始说人话了。
其实好好设计一下meta programming就能很简单解决的事情,偏偏过去20年基本就是不停地用语法糖叠加语法糖,靠各类畸形的模板库各类奇技淫巧硬凑。
【 在 DoorWay 的大作中提到: 】
: C++26 提案中推进的编译期 for 循环(template for)。
: 折叠表达式,可以被简化为下面这样:
: template<typename T>
: ...................
--
FROM 124.64.22.*