-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathpodout.h
More file actions
327 lines (285 loc) · 11.7 KB
/
podout.h
File metadata and controls
327 lines (285 loc) · 11.7 KB
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
/*
podout.h - a very quick and dirty reflection for pod objects using c++20
Sep. 2025
Author: Dr. Pufeng Du,
Notes: Basically, I reimplement a most basic and inefficient version of the
famous Boost:PFR:magic_get, which was developped by Antony Polukhin.
(https://github.com/apolukhin/magic_get). I do not speak russian. So I
spent some time watching his representation on cppcon16 and cppcon18 to
figure out key techs in that library. I included some technique known as
the friend injection or stateful meta programming, which was originally
proposed by Alexandr Poltavsky (https://alexpolt.github.io/type-loophole.
html). I do not believe that I can declare any kind of copyright on this
header. So there is no GNU GPL on this header.
This is a very quick and dirty implementation of the reflection of POD data.
The usage of this header is simple.
Step 1 - include the header.
Step 2 - define your own POD structs.
No array member is allowed in the struct.
Step 3 - use stream output operator to write all members to the stream
You can explore this simple header yourself. It has some basic facilities
for reflections. For example, you can obtain members using member names at
runtime. (These names must be assigned by yourself. I mean, any name, not
necessarily same as your definition in your POD struct.) The member values
will be returned as strings. There is a for_each function if want to use a
lambda to visit each member of a POD object. The usage of the for_each is
like:
reflect(pod_object).for_each(your_lambda);
If you want to call static member functions of the class_t<T>, you need to
first call reflect<T>() or reflect(t), if t is an object of type T.
Again, this is just a toy for juniors. Do not expect too much.
*/
#ifndef _PODOUT_H_
#define _PODOUT_H_
#include <iostream>
#include <sstream>
#include <utility>
#include <stdexcept>
#include <type_traits>
#include <tuple>
#include <array>
template <typename S>
constexpr auto reflect(const S &s = {});
template <typename S>
struct null_t{};
template <typename S>
bool is_reflected = false;
template <typename T>
concept is_stream_readable = requires (std::istream &is, T &a) {
is >> a;
};
template <typename T>
concept is_stream_writable = requires (std::ostream &os, const T &a) {
os << a;
};
template <typename S, typename... Ts>
concept is_aggregatable = requires (){ { S { Ts{}... } } -> std::same_as<S>; };
template<typename T>
std::string typename_to_string(){
std::string func_name = __PRETTY_FUNCTION__;
size_t _s = func_name.find("T = ") + 4;
size_t _e = func_name.find(";", _s);
std::string r = func_name.substr(_s, _e - _s);
return r;
}
template <typename MP>
struct extract_from_member_ptr;
template <typename M, typename S>
struct extract_from_member_ptr<M S::*>{
using class_type = std::remove_cvref_t<S>;
using member_type = std::remove_cvref_t<M>;
};
// Begin: scope_cast
// scope_cast<destination_scope_class>(source_scope_class_member_ptr)
// This is to convert any pod struct member ptr to another class pod member ptr
// at compile time. There is no restriction to the relationship between the two
// classes.
template <typename A, typename B>
struct class_link: public A, public B{};
// The member ptr p in the deep auto_t operator U was converted first by the
// injected friend funtions to the null_t<REF_STRUCT>, which is empty.
// By using the class_link, the member ptr of the null_t<REF_STRUCT> was
// transferred to the member ptr of the REF_STRUCT, which is TD in this func.
// All these type conversions happen at compile time.
template <typename TD>
constexpr auto scope_cast(const auto &p){
using MP = std::remove_cvref_t<decltype(p)>;
using D = std::remove_cvref_t<TD>;
using S = typename extract_from_member_ptr<MP>::class_type;
using M = typename extract_from_member_ptr<MP>::member_type;
using SL = class_link <D, S>;
static_assert(std::is_empty_v<S>,
"The scope_cast needs a member ptr in an empty class");
return static_cast<M (D::*)>(static_cast<M (SL::*)>(p));
};
// End: scope_cast
// Begin: Friend injections
// Notes: This is pure dark-arts. I mean it. This is not supported by the
// standard. If someday the committee decided to forbid such things, that
// means they finally cast a Avada Kedavra to us, who rely serioursly on this
// type loophole. Surely, C++26 meta/reflexpr will replace all of these.
// Stateful meta programming using the type loophole. We use friend injections
// to save types at compile time. Get yourself prepared to see many warnings...
// Or, you should add -Wno-non-template-friend to your compile options. I assume
// you are using GCC.
template <typename S>
struct class_tag_t{
friend auto constexpr struct_to_tuple(class_tag_t<S>);
};
template <typename S, typename Tp>
struct save_class_type_as_tuple{
save_class_type_as_tuple(){ is_reflected< S > = true; }
friend auto constexpr struct_to_tuple(class_tag_t<S>){ return Tp{}; }
};
template <typename S, size_t I>
struct member_tag_t{
friend auto constexpr member_ptr_type(member_tag_t<S, I>);
friend auto constexpr member_type(member_tag_t<S, I>);
};
template <typename S, size_t I, auto p>
struct save_member_type{
using MP = std::remove_cvref_t<decltype(p)>;
using M = typename extract_from_member_ptr<MP>::member_type;
using C = typename extract_from_member_ptr<MP>::class_type;
static_assert(std::is_base_of_v<null_t<S>, C>, "Unable to save pointers");
friend auto constexpr member_ptr_type(member_tag_t<S, I>){
return static_cast <M null_t<S>::* >(p);
}
friend auto constexpr member_type(member_tag_t<S, I>){ return M{}; }
};
// End: Friend injections
// Begin: POD reflection facilities
template <typename BS, typename TM>
struct member_type_sequence: public BS{
TM _v;
};
template <typename S, size_t n = 0, typename B = null_t<S>, typename... Ts>
requires (std::is_trivial_v<B> && std::is_standard_layout_v<S>)
struct auto_t{
template <typename U>
requires (std::is_standard_layout_v<U> && std::is_trivial_v<U>)
constexpr operator U() noexcept {
if constexpr (std::is_class_v<U>){
if (!is_reflected< U >)
reflect<U>();
}
// Add members according to S one after one to get member offsets. I
// have no idea whether this will be the same as S in all cases, if S is
// POD. Surely, this relies on optimizations for empty base classes.
// The member_sequence object is trivial. All offsets are obtained by
// inheritence to construct a struct according to S. The member pointer
// to each member is binary compatible to its offset.
using member_sequence = member_type_sequence<B, U>;
// This saves the n-th member of S by using pointer-to-member of
// member_sequence.
save_member_type<S, n, &member_sequence::_v>{};
// Try construction incrementally, to iterate over all members of S
// recursively.
using next_t = auto_t<S, n + 1, member_sequence, Ts..., U>;
if constexpr (is_aggregatable<S, Ts..., U, next_t> )
// By instantiating S with more initilizers, we use the U() function to
// iterate over types of all S members.
S { Ts{}..., U{}, next_t{} };
else
// When we are inside the last member's U(), we can save all types of
// members of S by using friend injections. Below struct MUST be called
// in this way or using the sizeof() expression, so that causing friend
// functions to be injected into the file scope.
save_class_type_as_tuple<S, std::tuple<Ts (S::*)..., U (S::*)> >{};
return U{};
}
};
template <typename S, size_t I>
struct member_t{
using type = decltype(member_type(member_tag_t<S, I>{}));
static constexpr type S::* ptr = scope_cast<S>(
member_ptr_type(member_tag_t<S, I>{})
);
static std::string name;
};
template <typename S, size_t I>
std::string member_t<S, I>::name;
template <typename S>
struct class_t{
class_t(const S &_o):_object(_o){
if (!is_reflected< S >)
reflect<S>();
}
const S &_object;
template <size_t... ids>
static constexpr auto get_ptrs(std::index_sequence<ids...>){
return tuple_type{ member_t<S, ids>::ptr... };
}
using tuple_type = decltype(struct_to_tuple(class_tag_t<S>{}));
static constexpr size_t member_count = std::tuple_size_v<tuple_type>;
using name_list_type = std::array<std::string, member_count>;
static constexpr tuple_type ptrs = get_ptrs(
std::make_index_sequence<member_count>{}
);
static name_list_type names;
template <size_t n = 0, typename N0 = std::string, typename... Args>
static auto set_member_names(N0 n0 = "", Args... ns){
if constexpr (n < member_count){
member_t<S, n>::name = n0;
names[n] = n0;
}
if constexpr (sizeof...(ns) > 0)
return set_member_names<n + 1>(ns...);
else
return n;
}
template <size_t n = 0, typename N = std::string>
std::string get_member_by_name_as_str(const N &nx){
if constexpr (n < member_count){
using member_type_as = decltype(_object.*std::get<n>(ptrs));
if (member_t<S, n>::name == nx){
if constexpr (is_stream_writable < member_type_as >)
return (std::stringstream() << (
_object.*std::get<n>(ptrs)
)).str();
else
return "";
}
return get_member_by_name_as_str<n + 1>(nx);
}
else
throw std::runtime_error("Unable to find member by name");
}
template <size_t n = 0, typename N = std::string>
void set_member_by_name_from_str(const N &nx, const std::string &s){
if constexpr (n < member_count){
using member_type_mutable = std::remove_cvref_t<
decltype(_object.*std::get<n>(ptrs))
>;
if (member_t<S, n>::name == nx) {
if constexpr ( is_stream_readable<member_type_mutable> )
std::stringstream(s) >> const_cast<member_type_mutable&>(
_object.*std::get<n>(ptrs)
);
return;
}
set_member_by_name_from_str< n + 1 >(nx, s);
}
else
throw std::runtime_error("Unable to find member by name");
}
template <size_t n = 0>
bool constexpr for_each(auto func){
if constexpr (n < member_count){
func(_object.*std::get<n>(ptrs));
return for_each< n + 1>(func);
}
else
return true;
}
};
template <typename S>
class_t<S>::name_list_type class_t<S>::names;
// End: POD reflection facilities
// Begin: Seiralization operators
template <typename S>
requires (std::is_class_v<S> &&
std::is_standard_layout_v<S> && std::is_trivial_v<S>)
std::ostream &operator<< (std::ostream &o, const S &a){
auto stream_out_action = [&](auto &p){ o << p << ' '; };
o << "[ ";
reflect(a).for_each(stream_out_action);
o << ']';
return o;
}
template <typename S>
requires (std::is_class_v<S> &&
std::is_standard_layout_v<S> && std::is_trivial_v<S>)
std::istream &operator>> (std::istream &i, S &a){
auto stream_in_action = [&](auto &p){ i >> p; };
reflect(a).for_each(stream_in_action);
return i;
}
// End: Seiralization operators
template <typename S>
constexpr auto reflect(const S &s){
if (!is_reflected< S >)
S { auto_t<S>{} };
return class_t<S>(s);
}
#endif //_PODOUT_H_