1 | #include <cassert>
|
2 | #include <tuple>
|
3 | #include <vector>
|
4 |
|
5 | // Ich brauche ein Template (oder Tips) wie ich von einer Dispatcher-Klasse aus
|
6 | // ohne viel Boilerplate-Code native Methoden aufrufen kann
|
7 | // Das ganze Konzept funktioniert (schon etwas länger) wie es soll
|
8 | // aber ich würde sehr gerne den Boilerplate Code reduzieren
|
9 |
|
10 | // Hier ein stark vereinfachtes Beispiel - es geht mir in keinster Weise
|
11 | // um das Grundkonzept (das erkennt man in diesem Beispiel nicht wirklich)
|
12 | // sonder nur um ein bisschen Template-Magic in der
|
13 | // itest_class::test2_waere_perfekt Methode - der Rest-Code
|
14 | // ist nur damit meine Frage nicht total abstrakt ist
|
15 | // und man auch was ausführen kann
|
16 |
|
17 | // mit up-to-date: VS2017 15.9.6 oder gcc 8.2, also (fast) alles was C++17 so hergibt
|
18 |
|
19 | struct i_value // Value-Schnittstelle fuer Konstanten,Expression,Referenzen,...
|
20 | {
|
21 | // INFO: Mein Framework stellt per Reflection-Prinzip magisch sicher das nur die selben Typen verbunden werden (void* ist bei mir 100% safe)
|
22 | virtual void set(const void* const value) = 0;
|
23 | virtual void get(void* const value) = 0;
|
24 | };
|
25 |
|
26 | // hier in diesem Beispiel reduziere ich das ganze auf "Variablen"
|
27 | // also Objekte die das i_value Interface implementierung und einen Value-Zustand haben
|
28 | template<typename ValueType>
|
29 | struct value_t : i_value
|
30 | {
|
31 | ValueType _value = ValueType();
|
32 | value_t(const ValueType& value) :_value(value) {}
|
33 |
|
34 | // hier ist normalerweise noch Absicherungs-Code drinn/dahinter/drumherum
|
35 | const ValueType& magical_safe_cast(const void* const value) const
|
36 | {
|
37 | return *reinterpret_cast<const ValueType*>(value);
|
38 | }
|
39 | ValueType& magical_safe_cast(void* value)
|
40 | {
|
41 | return const_cast<ValueType&>(static_cast<const value_t*>(this)->magical_safe_cast(value));
|
42 | }
|
43 |
|
44 | void set(const void* const value) override
|
45 | {
|
46 | set(magical_safe_cast(value));
|
47 | }
|
48 |
|
49 | void get(void* value) override
|
50 | {
|
51 | magical_safe_cast(value) = get();
|
52 | }
|
53 |
|
54 | void set(const ValueType& value)
|
55 | {
|
56 | _value = value;
|
57 | }
|
58 |
|
59 | ValueType& get()
|
60 | {
|
61 | return _value;
|
62 | }
|
63 | };
|
64 |
|
65 | // liefert den return-type einer Methode
|
66 | template <typename Return, typename Class, typename ... Parameter>
|
67 | Return get_return_type(Return(Class::*)(Parameter...));
|
68 |
|
69 | // liefert die parameter-typen einer Methode
|
70 | template <typename Class, typename Return, typename... Parameter>
|
71 | std::tuple<Parameter...> method_args(Return(Class::*)(Parameter...))
|
72 | {
|
73 | return std::tuple<Parameter...>();
|
74 | }
|
75 |
|
76 | // kleine Abstraktion um in, out und inout Verhalten zu erzeugen
|
77 | template<typename ValueType>
|
78 | struct in_parameter_t
|
79 | {
|
80 | ValueType _native_value = ValueType();
|
81 | i_value* _value = nullptr;
|
82 | in_parameter_t(i_value* value) :_value(value) {}
|
83 |
|
84 | void load()
|
85 | {
|
86 | _value->get(&_native_value);
|
87 | }
|
88 |
|
89 | void store() {} // nicht implementiert da IN
|
90 |
|
91 | ValueType& operator()()
|
92 | {
|
93 | return _native_value;
|
94 | }
|
95 | };
|
96 |
|
97 | template<typename ValueType>
|
98 | struct out_parameter_t
|
99 | {
|
100 | ValueType _native_value = ValueType();
|
101 | i_value* _value = nullptr;
|
102 | out_parameter_t(i_value* value) :_value(value) {}
|
103 |
|
104 | void load() {} // nicht implementiert da OUT
|
105 |
|
106 | void store()
|
107 | {
|
108 | _value->set(&_native_value);
|
109 | }
|
110 |
|
111 | ValueType& operator()()
|
112 | {
|
113 | return _native_value;
|
114 | }
|
115 | };
|
116 |
|
117 | template<typename ValueType>
|
118 | struct inout_parameter_t
|
119 | {
|
120 | ValueType _native_value = ValueType();
|
121 | i_value* _value = nullptr;
|
122 | inout_parameter_t(i_value* value) :_value(value) {}
|
123 |
|
124 | void load()
|
125 | {
|
126 | _value->get(&_native_value);
|
127 | }
|
128 |
|
129 | void store()
|
130 | {
|
131 | _value->set(&_native_value);
|
132 | }
|
133 |
|
134 | ValueType& operator()()
|
135 | {
|
136 | return _native_value;
|
137 | }
|
138 | };
|
139 |
|
140 | // traits um aus den Methoden/Result-Typen den richtigen Adapter zu bekommen
|
141 |
|
142 | template<typename ValueType>
|
143 | struct parameter_type_t {};
|
144 |
|
145 | template<>
|
146 | struct parameter_type_t<const int&>
|
147 | {
|
148 | using type = in_parameter_t<int>;
|
149 | };
|
150 |
|
151 | template<>
|
152 | struct parameter_type_t<const int>
|
153 | {
|
154 | using type = in_parameter_t<int>;
|
155 | };
|
156 |
|
157 | template<>
|
158 | struct parameter_type_t<const double&>
|
159 | {
|
160 | using type = in_parameter_t<double>;
|
161 | };
|
162 |
|
163 | template<>
|
164 | struct parameter_type_t<const double>
|
165 | {
|
166 | using type = in_parameter_t<double>;
|
167 | };
|
168 |
|
169 | template<>
|
170 | struct parameter_type_t<int&>
|
171 | {
|
172 | using type = inout_parameter_t<int>;
|
173 | };
|
174 |
|
175 | template<>
|
176 | struct parameter_type_t<double&>
|
177 | {
|
178 | using type = inout_parameter_t<double>;
|
179 | };
|
180 |
|
181 | // "native" classe von der ich Methoden per ueber die itest_class aufrufen will
|
182 | struct test_class
|
183 | {
|
184 | int value = 100;
|
185 |
|
186 | void test0() { return; }
|
187 | void test1(const int& x, const double& y) { return; }
|
188 | double test2(const int& x, const double& y, double& z)
|
189 | {
|
190 | double res = x + y;
|
191 | z = 10;
|
192 | return res;
|
193 | }
|
194 | int& test3() { return value; }
|
195 | };
|
196 |
|
197 | using iparameter = std::vector<i_value*>;
|
198 |
|
199 | // "native" class Wrapper/Dispatcher
|
200 | // mein Framework erlaubt magisch-dynamisch das Aufrufen dieser Dispatch-Funktionen (mit ivalue Parametern)
|
201 |
|
202 | struct itest_class
|
203 | {
|
204 | test_class object; // liegt normalerweise wo anders
|
205 |
|
206 | // Stufe 0: alles von Hand, maximal unsicher (auch wenn ich mit Tricks hier Fehler erkenne)
|
207 | void test2_von_hand(const iparameter& parameter, i_value* const result)
|
208 | {
|
209 | assert(parameter.size() == 3);
|
210 |
|
211 | int x = 0;
|
212 | double y = 0.0;
|
213 | double z = 0.0;
|
214 | double res = 0.0;
|
215 |
|
216 | // load
|
217 | parameter[0]->get(&x); // IN
|
218 | parameter[1]->get(&y); // IN
|
219 | parameter[2]->get(&z); // INOUT
|
220 |
|
221 | // run
|
222 | res = object.test2(x, y, z);
|
223 |
|
224 | // store
|
225 | result->set(&res);
|
226 | //parameter[0]->set(&x); // nicht noetig da IN parameter
|
227 | //parameter[1]->set(&y); // nicht noetig da IN parameter
|
228 | parameter[2]->set(&z);
|
229 | }
|
230 |
|
231 | // hier habe ich versucht micht reflektiv an der nativen-Methode zu orientieren
|
232 | // weniger Fehler, aber zu viel Code - nur eine Blaupause fuer die wohl notwendigen
|
233 | // Template-Helper
|
234 | void test2_bisschen_besser(const iparameter& parameter, i_value* const result)
|
235 | {
|
236 | // die Methoden-Signatur abgreifen
|
237 | using native_parameter_types = decltype(method_args(&test_class::test2));
|
238 | using native_return_type = decltype(get_return_type(&test_class::test2));
|
239 | static_assert(!std::is_same<native_return_type, void>::value, "void fehlt noch");
|
240 |
|
241 | assert(parameter.size() == std::tuple_size<native_parameter_types>::value);
|
242 |
|
243 | // diesen tuple muesste man doch zur kompilezeit generieren koennen, oder?
|
244 | using parameter_types_t = std::tuple
|
245 | <
|
246 | parameter_type_t<std::tuple_element<0, native_parameter_types>::type>::type,
|
247 | parameter_type_t<std::tuple_element<1, native_parameter_types>::type>::type,
|
248 | parameter_type_t<std::tuple_element<2, native_parameter_types>::type>::type
|
249 | >;
|
250 |
|
251 | // diese instanzen vielleicht in einem tupel halten - aber wie geht das?
|
252 | std::tuple_element<0, parameter_types_t>::type p0(parameter[0]);
|
253 | std::tuple_element<1, parameter_types_t>::type p1(parameter[1]);
|
254 | std::tuple_element<2, parameter_types_t>::type p2(parameter[2]);
|
255 |
|
256 | using res_t = out_parameter_t<native_return_type>;
|
257 | res_t res(result);
|
258 |
|
259 | // load - schoen homogen, kann man das zur kompilezeit expandieren?
|
260 | p0.load();
|
261 | p1.load();
|
262 | p2.load();
|
263 |
|
264 | // run
|
265 | // https://cpppatterns.com/patterns/apply-tuple-to-function.html // laut diesem tutorial kann man methoden auch mit tupels aufrufen
|
266 | res() = object.test2(p0(), p1(), p2());
|
267 | // wie koennte man native_return_type == void loesen? if constexpr?
|
268 |
|
269 | // store
|
270 | res.store(); // setzt was
|
271 |
|
272 | // schoen homogen, kann man das zur kompilezeit expandieren?
|
273 | p0.store(); // macht nichts
|
274 | p1.store(); // macht nichts
|
275 | p2.store(); // setzt was
|
276 | }
|
277 |
|
278 | void test2_waere_perfekt(const iparameter& parameter, i_value* const result)
|
279 | {
|
280 | //!!!
|
281 | //Ziel meine Frage: Wie kann ich so ein run_method Template implementieren um
|
282 | //mir den ganzen Boilerplate Code zu sparen
|
283 |
|
284 | // mein Gefühl sagt mir das so ein Template moeglich sein muesste
|
285 | //run_method(&object, &test_class::test2, parameter, result);
|
286 | }
|
287 | };
|
288 |
|
289 |
|
290 | int main()
|
291 | {
|
292 | // Dispatcher-Klasse instanzieren
|
293 | itest_class itest_object;
|
294 |
|
295 | value_t<int> x(11); // value by ref
|
296 | value_t<double> y(22.0); // value by ref
|
297 | value_t<double> z(44.0); // ref
|
298 | iparameter parameter{ &x, &y, &z };
|
299 |
|
300 | value_t<double> result(0);
|
301 |
|
302 | result.set(0.0);
|
303 | z.set(0.0);
|
304 | itest_object.test2_von_hand(parameter, &result);
|
305 | assert(result.get() == 33.0);
|
306 | assert(z.get() == 10.0);
|
307 |
|
308 | result.set(0.0);
|
309 | z.set(0.0);
|
310 | itest_object.test2_bisschen_besser(parameter, &result);
|
311 | assert(result.get() == 33.0);
|
312 | assert(z.get() == 10.0);
|
313 |
|
314 | //result.set(0.0);
|
315 | //z.set(0.0);
|
316 | //itest_object.test2_waere_perfekt(parameter, &result);
|
317 | //assert(result.get() == 33.0);
|
318 | //assert(z.get() == 10.0);
|
319 |
|
320 | return 0;
|
321 | }
|