Lambdas mit unterschiedlichen Arities
-
Ich hänge mal wieder an meinem mangelnden Wissen...
Ich möchte eine Möglichkeit für Anwender (m)einer Library bieten, eine Liste von internen Funktionen um eigene zu erweitern. Das soll durch Aufruf einer Registrierungsfunktion mit Übergabe eines Namens und einer Lambdafunktion möglich sein. Die Funktionen müssen
float
zurückliefern und können bis zu 7float
-Argumente erwarten - fix, nicht variabel -, die Arity ist also zwischen 0 und 7.Ich habe nun eine Implementation gefunden, die aber a) hässlich und unelegant ist und b) Schlupflöcher für Fehlbenutzung lässt:
// Copyright 2022 bla #include <cstdint> #include <iostream> #include <functional> #include <math.h> using std::cout; using std::endl; class D { public: using Dlambda0 = std::function<float()>; using Dlambda1 = std::function<float(float)>; using Dlambda2 = std::function<float(float, float)>; using Dlambda3 = std::function<float(float, float, float)>; using Dlambda4 = std::function<float(float, float, float, float)>; using Dlambda5 = std::function<float(float, float, float, float, float)>; using Dlambda6 = std::function<float(float, float, float, float, float, float)>; using Dlambda7 = std::function<float(float, float, float, float, float, float, float)>; D(const char *name, Dlambda0 dFn) : n(name), fn0 {std::move(dFn)} { arity = 0; } D(const char *name, Dlambda1 dFn) : n(name), fn1 {std::move(dFn)} { arity = 1; } D(const char *name, Dlambda2 dFn) : n(name), fn2 {std::move(dFn)} { arity = 2; } D(const char *name, Dlambda3 dFn) : n(name), fn3 {std::move(dFn)} { arity = 3; } D(const char *name, Dlambda4 dFn) : n(name), fn4 {std::move(dFn)} { arity = 4; } D(const char *name, Dlambda5 dFn) : n(name), fn5 {std::move(dFn)} { arity = 5; } D(const char *name, Dlambda6 dFn) : n(name), fn6 {std::move(dFn)} { arity = 6; } D(const char *name, Dlambda7 dFn) : n(name), fn7 {std::move(dFn)} { arity = 7; } float operator() () { return callForArity(nanf(""), nanf(""), nanf(""), nanf(""), nanf(""), nanf(""), nanf("")); } float operator() (float a) { return callForArity(a, nanf(""), nanf(""), nanf(""), nanf(""), nanf(""), nanf("")); } float operator() (float a, float b) { return callForArity(a, b, nanf(""), nanf(""), nanf(""), nanf(""), nanf("")); } float operator() (float a, float b, float c) { return callForArity(a, b, c, nanf(""), nanf(""), nanf(""), nanf("")); } float operator() (float a, float b, float c, float d) { return callForArity(a, b, c, d, nanf(""), nanf(""), nanf("")); } float operator() (float a, float b, float c, float d, float e) { return callForArity(a, b, c, d, e, nanf(""), nanf("")); } float operator() (float a, float b, float c, float d, float e, float f) { return callForArity(a, b, c, d, e, f, nanf("")); } float operator() (float a, float b, float c, float d, float e, float f, float g) { return callForArity(a, b, c, d, e, f, g); } protected: const char *n; Dlambda0 fn0; Dlambda1 fn1; Dlambda2 fn2; Dlambda3 fn3; Dlambda4 fn4; Dlambda5 fn5; Dlambda6 fn6; Dlambda7 fn7; uint8_t arity; float callForArity(float a, float b, float c, float d, float e, float f, float g) { switch (arity) { case 0: return fn0(); break; case 1: return fn1(a); break; case 2: return fn2(a, b); break; case 3: return fn3(a, b, c); break; case 4: return fn4(a, b, c, d); break; case 5: return fn5(a, b, c, d, e); break; case 6: return fn6(a, b, c, d, e, f); break; case 7: return fn7(a, b, c, d, e, f, g); break; } return nanf(""); } }; int main(int argc, char **argv) { // So etwa soll die Registrierung neuer Funktionen gehen D funktion("bla", { []() { return 5; }}); D funktion2("blubb", { [](float x, float y) { return x + y; }}); cout << funktion() << endl; // Okay cout << funktion2(1.0, 5.5) << endl; // Okay cout << funktion2(5.5) << endl; // Aua... return 0; }
Das compiliert und läuft zwar (G++, std=c++11), aber knallt natürlich beim letzten Funktionsaufruf, weil der benutzte
fn2
-Funktionspointer zwei Argumente erwartet und ersatzweise ein NaN bekommt.Ich möchte das eigentlich schon zur Compilezeit verhindert haben.
Wie stelle ich das besser an?
-
Das müsste doch mit variadic templates und type_traits gehen.
-
@DocShoe sagte in Lambdas mit unterschiedlichen Arities:
Das müsste doch mit variadic templates und type_traits gehen.
Uncharted land... Variadic template: okay, type traits: nada bei mir.
-
Ich verstehe den gesamten Hintergrund hier nicht. Was Du anbieten willst, nämlich einen type erased Funktor, gibt es laengst, std::function. Nur dass die Anzahl der Argumente i.d.R. fix ist. Andererseits macht es selten Sinn, variadische Funktoren herumzureichen, weil ja am Ende immer eine bestimmte Zahl von Parametern tatsaechlich eingesetzt wird.
Also: warum ist die Aritaet tatsaechlich variabel? Und warum kann man nicht einfach aus den Parametern eine Klasse
Param
machen, die ein Array aus 7float
s hat und einensize
Member?