| 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 | }
 |