第六章:编译期反射 private 成员
上一章我们已经介绍了对于聚合类型的编译期反射,本章我们介绍对于非聚合类型如何反射。
首先看一下知乎的 YKIKO 在有状态黑魔法中提到的例子:
👉 https://zhuanlan.zhihu.com/p/646752343
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #include <iostream>
class Bank { double money = 999'999'999'999;
public: void check() const { std::cout << money << std::endl; } };
template <auto mp> struct Thief { };
template struct Thief<&Bank::money>;
|
这里的 &Bank::money
是一个成员变量指针,不同于普通的指针,它并不依赖于某个对象,而表示该成员在类中的偏移量,是一个编译期常量。
在 C++ 中,成员指针(Pointer-to-Member) 和 普通指针(Pointer-to-Object/Pointer-to-Function) 是两种不同的概念,它们的类型、语法和用途都有显著区别。下面详细对比它们的差异:
1. 类型定义
(1)普通指针
- 指向对象:
T*
(指向类型 T
的对象)
- 指向函数:
R (*)(Args...)
(指向返回 R
、参数为 Args...
的函数)
- 示例:
1 2 3 4 5
| int x = 10; int* ptr = &x;
void foo(int); void (*func_ptr)(int) = &foo;
|
(2)成员指针
- 指向成员变量:
T C::*
(指向类 C
的成员变量,类型为 T
)
- 指向成员函数:
R (C::*)(Args...)
(指向类 C
的成员函数)
- 示例:
1 2 3 4 5 6 7
| struct Bank { double money; void check() const; };
double Bank::* money_ptr = &Bank::money; void (Bank::* check_ptr)() const = &Bank::check;
|
2. 使用方式
(1)普通指针
- 直接解引用访问:
1 2 3
| int x = 10; int* ptr = &x; *ptr = 20;
|
(2)成员指针
- 必须结合对象 才能访问:
1 2 3 4 5 6
| Bank bank; double Bank::* money_ptr = &Bank::money; bank.*money_ptr = 100;
void (Bank::* check_ptr)() const = &Bank::check; (bank.*check_ptr)();
|
3. 存储方式
特性 |
普通指针 |
成员指针 |
存储地址 |
直接存储对象/函数的地址 |
存储的是 相对于类对象的偏移量(成员变量)或 函数地址+调整信息(成员函数) |
是否依赖对象 |
可以直接解引用 |
必须绑定到对象才能使用(obj.*ptr 或 obj->*ptr ) |
sizeof 大小 |
通常等于机器字长(如 8 字节) |
可能比普通指针大(成员函数指针可能占用 2 个机器字) |
👀 显示实例化与私有访问权限
这里还用到了模板显示实例化时会忽略类作用域访问权限的特性。
🔍 什么是显示实例化?
1 2
| template class MyClass<int>; template void myFunc<double>();
|
这强制编译器在该位置生成实例代码,而不是等到首次使用。
✅ 优点
1 2
| template class MyClass<int>;
|
其他文件:
1
| extern template class MyClass<int>;
|
⚠️ 无法访问 private 成员的问题
但是问题来了,即使我们显示实例化了Thief<&Bank::money>,编译器也帮我们生成了这样的代码:
1 2 3 4 5
| template <&Bank::money> struct Thief { };
|
你甚至已经想好了利用Thief的类型参数做各种操作:
1 2 3 4 5 6
| template <mp = &Bank::money> struct Thief { double& steal(Bank& bank) { return bank.*mp; } static double& steal2(Bank& bank) { return bank.*mp; } };
|
但是有什么用呢,你无法使用Thief中的任何成员函数,因为Thief本身是一个类模板,想使用Thief必须要指定类型参数。
一旦你指定了Thief<&Bank::money>去隐式实例化一个Thief,会立马报错因为无法访问&Bank::money。
那么我显示实例化生成的代码岂不是卵用没有?
这就需要结合友元函数来使用,友元函数与普通函数最大的不同就在于不要求函数定义与函数声明在同一scope中,所以我们就可以非Thief的作用域下使用它。
具体来说就是在全局的scope去声明该友元函数;而在Thief<&Bank::money>的scope去定义友元函数,同时窃取它的非类型参数,这样我们就可以在全局的scope去使用它,绕过了必须隐式实例化Thief才能使用它的限制。
🪄 解决方案:友元函数 + 显示实例化
利用友元函数的特性,可以在类模板内部定义,在外部声明,从而绕开访问权限。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #include <iostream>
class Bank { double money = 999'999'999'999;
public: void check() const { std::cout << money << std::endl; } };
template <auto mp> struct Thief { friend double& steal(Bank& bank) { return bank.*mp; } };
double& steal(Bank& bank); template struct Thief<&Bank::money>;
int main() { Bank bank; steal(bank) = 100; bank.check(); }
|
我曾经幻想利用这个机制实现一个thief的库,把显式实例化和友元函数都封装在库中,用户只需注册成员就可以直接使用库中的steal方法获取私有成员。
1 2 3 4 5 6 7 8
| int main() { Bank bank; REGISTER(Bank, money); steal(bank) = 100; bank.check(); }
|
但是显式实例化必须在全局命名空间 或 命名空间作用域,不能出现在函数内部:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| #include <iostream>
template <auto mp> struct Thief { template <typename T> friend double& steal(T&& t) { return t.*mp; } }; template <typename T> double& steal(T&& t);
#define REGISTER(STRUCT, MEMBER) \ template struct Thief<&STRUCT::MEMBER>;
class Bank { double money = 999'999'999'999;
public: void check() const { std::cout << money << std::endl; } };
int main() { Bank bank; REGISTER(Bank, money); steal(bank) = 100; bank.check(); }
|
这是因为显式实例化声明和定义(如 template struct Thief<&Bank::money>;
)在 C++ 标准中被限定只能出现在命名空间作用域(即:全局作用域或某个命名空间中),不能放在函数体(比如 main
)里。这是出于以下几个原因:
✅ 1. 语言标准限制(语法层面)
C++ 标准([C++20 §14.7.2])明确指出:
An explicit instantiation shall appear in a namespace scope (not inside a function or class).
这是语法层面的硬性规定,编译器会直接报错,不予接受。
✅ 2. 链接模型设计:实例化必须唯一可见
显式实例化通常意味着「我手动生成这个模板的具体版本,别再自己自动生成了」,这影响符号的生成和链接行为:
- 如果你允许它在函数内,会在函数作用域内临时生成一个实例;
- 但别的地方可能还会再实例化一次,违反 One Definition Rule(ODR);
- 放在全局作用域可以控制符号的唯一性,符合链接器模型。
🧩 泛化实现:反射 private 成员
1 2 3 4 5 6 7 8 9 10 11
| template <typename T, auto... field> struct private_visitor { friend inline constexpr auto get_private_ptrs() { constexpr auto tp = std::make_tuple(field...); return tp; } };
#define REFL_PRIVATE(STRUCT, ...) \ inline constexpr auto get_private_ptrs(); \ template struct private_visitor<STRUCT, ##__VA_ARGS__>;
|
示例:
1 2 3 4 5 6
| class PersonPrivate { Field<std::string> m_name; Field<int> m_age; bool m_male; }; REFL_PRIVATE(PersonPrivate, &PersonPrivate::m_name, &PersonPrivate::m_age, &PersonPrivate::m_male)
|
🧭 为了避免 ODR 冲突 —— 添加类型参数
1 2 3 4 5 6 7 8 9 10 11
| template <typename T, auto... field> struct private_visitor { friend inline constexpr auto get_private_ptrs(const my_wrapper<T>&) { constexpr auto tp = std::make_tuple(field...); return tp; } };
#define REFL_PRIVATE(STRUCT, ...) \ inline constexpr auto get_private_ptrs(const my_wrapper<STRUCT> &t); \ template struct private_visitor<STRUCT, ##__VA_ARGS__>;
|
🧠 SFINAE 萃取成员类型
我们可以从成员指针中推导其类型:
1 2 3 4 5 6 7 8 9 10 11
| template <auto MemberPtr> struct MemberPointerTraits;
template <typename T, typename C, T C::*MemberPtr> struct MemberPointerTraits<MemberPtr> { using type = T; using class_type = C; };
template <auto MemberPtr> using member_value_v = typename MemberPointerTraits<MemberPtr>::type;
|
至此,我们实现了:
- ✅ 聚合类型的反射
- ✅ 非聚合类型的反射
- ✅ 编译期萃取字段类型
- ✅ 编译期获取字段名称
真正意义上的非侵入式编译期反射机制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
| #include <iostream>
template <typename T> struct Field {};
template <class T> struct my_wrapper { inline static T value; };
template <typename T, auto... field> struct private_visitor { friend inline constexpr auto get_private_ptrs(const my_wrapper<T>&) { constexpr auto tp = std::make_tuple(field...); return tp; } };
#define REFL_PRIVATE(STRUCT, ...) \ inline constexpr auto get_private_ptrs(const my_wrapper<STRUCT> &t); \ template struct private_visitor<STRUCT, ##__VA_ARGS__>;
struct Dog { bool m_male; };
struct Person { bool m_male; Field<std::string> m_name; Field<int> m_age; };
class PersonPrivate { Field<std::string> m_name; Field<int> m_age; bool m_male; }; REFL_PRIVATE(PersonPrivate, &PersonPrivate::m_name, &PersonPrivate::m_age, &PersonPrivate::m_male)
struct AnyType { template <typename T> operator T(); };
template <typename T> consteval size_t countMember(auto&&... Args) { if constexpr (!requires { T{ Args... }; }) { return sizeof...(Args) - 1; } else { return countMember<T>(Args..., AnyType{}); } }
template <typename T> constexpr size_t member_count_v = countMember<T>();
template <class T> inline constexpr T& get_global_object() noexcept { return my_wrapper<T>::value; }
template <typename T> struct Is_Field : std::false_type { };
template <typename T> struct Is_Field<Field<T>> : std::true_type {};
template <typename Tuple> constexpr bool check_field(const Tuple& tuple) { bool found = false;
std::apply([&](const auto&... args) { ((found = found || Is_Field<std::decay_t<decltype(args)>>()), ...); }, tuple);
return found; }
template <auto ptr> void f() { #if defined(__GNUC__) constexpr std::string_view func_name = __PRETTY_FUNCTION__; #elif defined(_MSC_VER) constexpr std::string_view func_name = __FUNCSIG__; #endif std::cout << func_name << std::endl; }
template <typename T, std::size_t n> struct ReflectHelper{};
#define RFL_STRUCT(n, ...) \ template <class T> \ struct ReflectHelper<T, n> { \ static constexpr auto reflectFieldImpl() { \ auto& [__VA_ARGS__] = get_global_object<T>(); \ auto ref_tup = std::tie(__VA_ARGS__); \ return check_field(ref_tup); \ } \ }
RFL_STRUCT(1, f0); RFL_STRUCT(2, f0, f1); RFL_STRUCT(3, f0, f1, f2); RFL_STRUCT(4, f0, f1, f2, f3); RFL_STRUCT(5, f0, f1, f2, f3, f4);
template <auto ptr> inline constexpr std::string_view get_member_name() { #if defined(__GNUC__) constexpr std::string_view func_name = __PRETTY_FUNCTION__; #elif defined(_MSC_VER) constexpr std::string_view func_name = __FUNCSIG__; #endif return func_name; }
template <auto MemberPtr> struct MemberPointerTraits;
template <typename T, typename C, T C::*MemberPtr> struct MemberPointerTraits<MemberPtr> { using type = T; using class_type = C; };
template <auto MemberPtr> using member_value_v = typename MemberPointerTraits<MemberPtr>::type;
template <typename T> concept IsAggregate = std::is_aggregate_v<T>;
template <typename T> struct ReflectField { static constexpr bool reflect() { constexpr auto tp = get_private_ptrs(my_wrapper<T>{}); constexpr size_t N = std::tuple_size_v<decltype(tp)>; bool found = false;
[&]<size_t... Is>(std::index_sequence<Is...>) { ((found = found || Is_Field<member_value_v<std::get<Is>(tp)>>()), ...); }(std::make_index_sequence<N>{});
return found; } };
template <IsAggregate T> struct ReflectField<T> { static constexpr auto reflect() { return ReflectHelper<T, member_count_v<T>>::reflectFieldImpl(); } };
template <typename T> constexpr bool reflectField_v = ReflectField<T>::reflect();
int main() { static_assert(!reflectField_v<Dog>); static_assert(reflectField_v<Person>); static_assert(reflectField_v<PersonPrivate>); constexpr auto tp = get_private_ptrs(my_wrapper<PersonPrivate>{}); [&]<size_t... Is>(std::index_sequence<Is...>) { (std::cout << ... << get_member_name<std::get<Is>(tp)>()); }(std::make_index_sequence<std::tuple_size_v<decltype(tp)>>{}); }
|