-
Notifications
You must be signed in to change notification settings - Fork 13
/
type-loophole.h
151 lines (108 loc) · 4.77 KB
/
type-loophole.h
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
/*
The Great Type Loophole (C++14)
Author: Alexandr Poltavsky, http://alexpolt.github.io
License: Public-domain software
Description:
The Great Type Loophole is a technique that allows to exchange type information with template
instantiations. Basically you can assign and read type information during compile time.
Here it is used to detect data members of a data type. I described it for the first time in
this blog post http://alexpolt.github.io/type-loophole.html .
Before this, I've created a Struct Reader to get data member types from a struct,but it can only
work with literal types from a fixed list. This can detect any types in a struct (except for
references and cv-qualified types) with nothing but C++ (C++14). It doesn't require a pre-built
type list. It uses list initialization for this and because of that it is limited to aggregate
types. You can try it on something with a constructor but it will most likely fail (especially
if there are constructors with an std::initializer_list).
Dependencies:
luple.h (a lightweight tuple): luple_t, luple_ns::type_list (bare template with a parameter pack)
utility: std::integer_sequence
Usage:
#include "type-loophole.h"
struct test {
test() {}
};
struct data {
test t0;
std::string t1;
std::vector<int> t2;
};
using data_tlist = loophole_ns::as_type_list< data >; // type_list< test, std::string, std::vector >
//luple_t<...> - takes luple_ns::type_list< list of types >, luple<...> takes a list of types directly
//for more information refer to luple.h
using data_luple = luple_t< data_tlist >; //check luple.h for API
data d{ {}, "Hello World!", {1, 2 ,3} };
auto& l = reinterpret_cast< data_luple& >
auto sz = size( data_luple );
printf( "%s\n", get< std::string >( l ).data() );
for( auto i : get< 2 >( l ) ) printf( "%d, ",i );
You can find links to online working examples in the blog post
http://alexpolt.github.io/type-loophole.html
*/
#include "luple.h"
namespace loophole_ns {
/*
tag<T,N> generates friend declarations and helps with overload resolution.
There are two types: one with the auto return type, which is the way we read types later.
The second one is used in the detection of instantiations without which we'd get multiple
definitions.
*/
template<typename T, int N>
struct tag {
friend auto loophole(tag<T,N>);
constexpr friend int cloophole(tag<T,N>);
};
/*
The definitions of friend functions.
*/
template<typename T, typename U, int N, bool B>
struct fn_def {
friend auto loophole(tag<T,N>) { return U{}; }
constexpr friend int cloophole(tag<T,N>) { return 0; }
};
/*
This specialization is to avoid multiple definition errors.
*/
template<typename T, typename U, int N>
struct fn_def<T, U, N, true> {};
/*
This has a templated conversion operator which in turn triggers instantiations.
Important point, using sizeof seems to be more reliable. Also default template
arguments are "cached" (I think). To fix that I provide a U template parameter to
the ins functions which do the detection using constexpr friend functions and SFINAE.
*/
template<typename T, int N>
struct c_op {
template<typename U, int M> static auto ins(...) -> int;
template<typename U, int M, int = cloophole(tag<T,M>{}) > static auto ins(int) -> char;
template<typename U, int = sizeof(fn_def<T, U, N, sizeof(ins<U, N>(0)) == sizeof(char)>)>
operator U();
};
/*
Here we detect the data type field number. The byproduct is instantiations.
Uses list initialization. Won't work for types with user provided constructors.
In C++17 there is std::is_aggregate which can be added later.
*/
template<typename T, int... NN>
constexpr int fields_number(...) { return sizeof...(NN)-1; }
template<typename T, int... NN>
constexpr auto fields_number(int) -> decltype(T{ c_op<T,NN>{}... }, 0) {
return fields_number<T, NN..., sizeof...(NN)>(0);
}
/*
This is a helper to turn a data structure into a type list.
Usage is: loophole_ns::as_type_list< data_t >
I keep dependency on luple (a lightweight tuple of my design) because it's handy
to turn a structure into luple (tuple). luple has the advantage of more stable layout
across compilers and we can reinterpret_cast between the data structure and luple.
More details are in the luple.h header.
*/
template<typename T, typename U>
struct loophole_type_list;
template<typename T, int... NN>
struct loophole_type_list< T, std::integer_sequence<int, NN...> > {
using type = luple_ns::type_list< decltype(loophole(tag<T, NN>{}))... >;
};
template<typename T>
using as_type_list =
typename loophole_type_list<T, std::make_integer_sequence<int, fields_number<T>(0)>>::type;
}