#include <cassert>
#include <tuple>
#include <vector>
// Ich brauche ein Template (oder Tips) wie ich von einer Dispatcher-Klasse aus
// ohne viel Boilerplate-Code native Methoden aufrufen kann
// Das ganze Konzept funktioniert (schon etwas lC$nger) wie es soll
// aber ich wC<rde sehr gerne den Boilerplate Code reduzieren
// Hier ein stark vereinfachtes Beispiel - es geht mir in keinster Weise
// um das Grundkonzept (das erkennt man in diesem Beispiel nicht wirklich)
// sonder nur um ein bisschen Template-Magic in der
// itest_class::test2_waere_perfekt Methode - der Rest-Code
// ist nur damit meine Frage nicht total abstrakt ist
// und man auch was ausfC<hren kann
// mit up-to-date: VS2017 15.9.6 oder gcc 8.2, also (fast) alles was C++17 so hergibt
struct i_value // Value-Schnittstelle fuer Konstanten,Expression,Referenzen,...
{
// INFO: Mein Framework stellt per Reflection-Prinzip magisch sicher das nur die selben Typen verbunden werden (void* ist bei mir 100% safe)
virtual void set(const void* const value) = 0;
virtual void get(void* const value) = 0;
};
// hier in diesem Beispiel reduziere ich das ganze auf "Variablen"
// also Objekte die das i_value Interface implementierung und einen Value-Zustand haben
template<typename ValueType>
struct value_t : i_value
{
ValueType _value = ValueType();
value_t(const ValueType& value) :_value(value) {}
// hier ist normalerweise noch Absicherungs-Code drinn/dahinter/drumherum
const ValueType& magical_safe_cast(const void* const value) const
{
return *reinterpret_cast<const ValueType*>(value);
}
ValueType& magical_safe_cast(void* value)
{
return const_cast<ValueType&>(static_cast<const value_t*>(this)->magical_safe_cast(value));
}
void set(const void* const value) override
{
set(magical_safe_cast(value));
}
void get(void* value) override
{
magical_safe_cast(value) = get();
}
void set(const ValueType& value)
{
_value = value;
}
ValueType& get()
{
return _value;
}
};
// liefert den return-type einer Methode
template <typename Return, typename Class, typename ... Parameter>
Return get_return_type(Return(Class::*)(Parameter...));
// liefert die parameter-typen einer Methode
template <typename Class, typename Return, typename... Parameter>
std::tuple<Parameter...> method_args(Return(Class::*)(Parameter...))
{
return std::tuple<Parameter...>();
}
// kleine Abstraktion um in, out und inout Verhalten zu erzeugen
template<typename ValueType>
struct in_parameter_t
{
ValueType _native_value = ValueType();
i_value* _value = nullptr;
in_parameter_t(i_value* value) :_value(value) {}
void load()
{
_value->get(&_native_value);
}
void store() {} // nicht implementiert da IN
ValueType& operator()()
{
return _native_value;
}
};
template<typename ValueType>
struct out_parameter_t
{
ValueType _native_value = ValueType();
i_value* _value = nullptr;
out_parameter_t(i_value* value) :_value(value) {}
void load() {} // nicht implementiert da OUT
void store()
{
_value->set(&_native_value);
}
ValueType& operator()()
{
return _native_value;
}
};
template<typename ValueType>
struct inout_parameter_t
{
ValueType _native_value = ValueType();
i_value* _value = nullptr;
inout_parameter_t(i_value* value) :_value(value) {}
void load()
{
_value->get(&_native_value);
}
void store()
{
_value->set(&_native_value);
}
ValueType& operator()()
{
return _native_value;
}
};
// traits um aus den Methoden/Result-Typen den richtigen Adapter zu bekommen
template<typename ValueType>
struct parameter_type_t {};
template<>
struct parameter_type_t<const int&>
{
using type = in_parameter_t<int>;
};
template<>
struct parameter_type_t<const int>
{
using type = in_parameter_t<int>;
};
template<>
struct parameter_type_t<const double&>
{
using type = in_parameter_t<double>;
};
template<>
struct parameter_type_t<const double>
{
using type = in_parameter_t<double>;
};
template<>
struct parameter_type_t<int&>
{
using type = inout_parameter_t<int>;
};
template<>
struct parameter_type_t<double&>
{
using type = inout_parameter_t<double>;
};
// "native" classe von der ich Methoden per ueber die itest_class aufrufen will
struct test_class
{
int value = 100;
void test0() { return; }
void test1(const int& x, const double& y) { return; }
double test2(const int& x, const double& y, double& z)
{
double res = x + y;
z = 10;
return res;
}
int& test3() { return value; }
};
using iparameter = std::vector<i_value*>;
// "native" class Wrapper/Dispatcher
// mein Framework erlaubt magisch-dynamisch das Aufrufen dieser Dispatch-Funktionen (mit ivalue Parametern)
struct itest_class
{
test_class object; // liegt normalerweise wo anders
// Stufe 0: alles von Hand, maximal unsicher (auch wenn ich mit Tricks hier Fehler erkenne)
void test2_von_hand(const iparameter& parameter, i_value* const result)
{
assert(parameter.size() == 3);
int x = 0;
double y = 0.0;
double z = 0.0;
double res = 0.0;
// load
parameter[0]->get(&x); // IN
parameter[1]->get(&y); // IN
parameter[2]->get(&z); // INOUT
// run
res = object.test2(x, y, z);
// store
result->set(&res);
//parameter[0]->set(&x); // nicht noetig da IN parameter
//parameter[1]->set(&y); // nicht noetig da IN parameter
parameter[2]->set(&z);
}
// hier habe ich versucht micht reflektiv an der nativen-Methode zu orientieren
// weniger Fehler, aber zu viel Code - nur eine Blaupause fuer die wohl notwendigen
// Template-Helper
void test2_bisschen_besser(const iparameter& parameter, i_value* const result)
{
// die Methoden-Signatur abgreifen
using native_parameter_types = decltype(method_args(&test_class::test2));
using native_return_type = decltype(get_return_type(&test_class::test2));
static_assert(!std::is_same<native_return_type, void>::value, "void fehlt noch");
assert(parameter.size() == std::tuple_size<native_parameter_types>::value);
// diesen tuple muesste man doch zur kompilezeit generieren koennen, oder?
using parameter_types_t = std::tuple
<
parameter_type_t<std::tuple_element<0, native_parameter_types>::type>::type,
parameter_type_t<std::tuple_element<1, native_parameter_types>::type>::type,
parameter_type_t<std::tuple_element<2, native_parameter_types>::type>::type
>;
// diese instanzen vielleicht in einem tupel halten - aber wie geht das?
std::tuple_element<0, parameter_types_t>::type p0(parameter[0]);
std::tuple_element<1, parameter_types_t>::type p1(parameter[1]);
std::tuple_element<2, parameter_types_t>::type p2(parameter[2]);
using res_t = out_parameter_t<native_return_type>;
res_t res(result);
// load - schoen homogen, kann man das zur kompilezeit expandieren?
p0.load();
p1.load();
p2.load();
// run
// https://cpppatterns.com/patterns/apply-tuple-to-function.html // laut diesem tutorial kann man methoden auch mit tupels aufrufen
res() = object.test2(p0(), p1(), p2());
// wie koennte man native_return_type == void loesen? if constexpr?
// store
res.store(); // setzt was
// schoen homogen, kann man das zur kompilezeit expandieren?
p0.store(); // macht nichts
p1.store(); // macht nichts
p2.store(); // setzt was
}
void test2_waere_perfekt(const iparameter& parameter, i_value* const result)
{
//!!!
//Ziel meine Frage: Wie kann ich so ein run_method Template implementieren um
//mir den ganzen Boilerplate Code zu sparen
// mein GefC<hl sagt mir das so ein Template moeglich sein muesste
//run_method(&object, &test_class::test2, parameter, result);
}
};
int main()
{
// Dispatcher-Klasse instanzieren
itest_class itest_object;
value_t<int> x(11); // value by ref
value_t<double> y(22.0); // value by ref
value_t<double> z(44.0); // ref
iparameter parameter{ &x, &y, &z };
value_t<double> result(0);
result.set(0.0);
z.set(0.0);
itest_object.test2_von_hand(parameter, &result);
assert(result.get() == 33.0);
assert(z.get() == 10.0);
result.set(0.0);
z.set(0.0);
itest_object.test2_bisschen_besser(parameter, &result);
assert(result.get() == 33.0);
assert(z.get() == 10.0);
//result.set(0.0);
//z.set(0.0);
//itest_object.test2_waere_perfekt(parameter, &result);
//assert(result.get() == 33.0);
//assert(z.get() == 10.0);
return 0;
}