In Teil 1 ging es um die allgemeinen Probleme einer simplen for() Schleife.
In Teil 2 werde ich Vittorio Romeo Ansatz verfolgen, eine repeat() Funktion zu erstellen, die eine Funktion f genau N mal aufruft.
Syntaktisch und semantisch würde ich diesen Code okay finden:
#include <type_traits> #include <iostream> void f() { cout << "f()\n"; } template<typename Func> void repeat(int n, Func&& f) { for(int i=0; i < n; ++i) { f(); } } int main() { repeat(10, f); }
Die Funktion f wird einfach 10 mal aufgerufen. Das war einfach.
Sobald ich mehr tun will, wirds kompliziert.
Schritt für Schritt. Wir werden sehen wann es nicht mehr klappt.
Möchte ich der Funktion f einen Parameter mitgeben, der nicht von der aktuellen Iterationen abhängt, lässt sich das leicht mit einem Lambda realisieren, welches ein Interface darstellt. Dieses Interface bildet meine Funktion f(int) auf eine Funktion ohne Argument ab. Also genau so eine Funktion, welche repeat() erwartet.
void f(int parameter) { cout << "f() parameter " << parameter << "\n"; } int main() { int parameter = 42; repeat(10, [parameter]() { f(parameter); }); }
Die Funktion repeat() ändert sich nicht. Das ist schon einmal ein gutes Zeichen.
Jetzt wird es kritisch. Ich möchte neben dem Parameter noch die Laufvariablen der repeat Funktion mit nach f() übergeben. Dafür benutze ich wieder ein Lambda welches das Interface bereit stellt und auch if constexpr und std::is_invocable_v. Also hight end C++17 Shit :D Mal sehn obs kappt.
void f(int i, int parameter) { cout << "f() i: " << i << " parameter: " << parameter << "\n"; } template<typename Func> void repeat(int n, Func&& f) { for(int i=0; i < n; ++i) { if constexpr(std::is_invocable_v<Func&&, int>) { f(i); } else { f(); } } } int main() { int parameter = 42; repeat(10, [parameter](int i) { f(parameter, i); }); }
Der Code compiliert mit einem C++17 Compiler (gcc 7) und liefert folgende Ausgabe:
f() i: 42 parameter: 0
f() i: 42 parameter: 1
f() i: 42 parameter: 2
f() i: 42 parameter: 3
f() i: 42 parameter: 4
f() i: 42 parameter: 5
f() i: 42 parameter: 6
f() i: 42 parameter: 7
f() i: 42 parameter: 8
f() i: 42 parameter: 9
Das ist nicht das, was ich wollte. Ich habe absichtlich die Funktions Argumente parameter
und i
vertauscht und der Compiler hats nicht gemerkt! Wie sollte er auch. Die Variable i ist nur ein int Type und hat keinerlei Semantik einer Laufvariablen.
Das hat mich am Artikel "abstraction design and implementation: `repeat`" auch so stark gestört. Damit es ist super einfach lazy Bugs zu erzeugen und am Ende hat man nichts gewonnen.
Im dritten Teil will ich versuchen das C++ Typesystem zu nutzen, um die Semantik einer Laufvariablen auszudrücken und im letzten Beispiel einen Compilerfehler zu erzwingen.