iX 3/2016
S. 50
Titel
Programmiersprachen
Aufmacherbild

Funktionale Programmierung in C++

Form follows Function

Für viele ist C++ gleichbedeutend mit objektorientierter Programmierung. Dennoch lassen sich viele Aufgaben auch mit funktionalen Techniken lösen. Ein Vergleich mit der rein funktionalen Programmiersprache Haskell zeigt, welche funktionalen Aspekte C++ heute schon hat. Mit C++17 sollen es sogar noch mehr werden.

C++ ist eine Multiparadigmen-Programmiersprache. Sie erlaubt es dem Programmierer, seine Aufgaben mit strukturierten, prozeduralen, objektorientierten, generischen sowie funktionalen Techniken zu lösen. Insbesondere die funktionalen Aspekte wurden mit Lambda-Funktionen, der automatischen Typableitung und der partiellen Funktionsanwendung in C++11 deutlich mächtiger. Ein Trend, der sich mit dem neuen C++17-Standard weiter verstärkt. Grund genug, die funktionalen Aspekte des modernen C++ herauszuarbeiten. Zum Vergleich zieht dieser Artikel die rein funktionale Programmiersprache Haskell heran.

Obwohl C++ keine funktionale Programmiersprache ist, erlaubt sie es, in einer funktionalen Art zu programmieren. Ein Beispiel ist die für funktionale Programmiersprachen typische automatische Typableitung. Sie gehört zu den meistgenutzten Features in C++11:

std::vector<int> myVec;
auto itVec= myVec.begin();
for ( auto v: myVec ) std::cout << v << " ";

In diesem Codeschnipsel wird der Typ itVec zum Iterator und v zur Ganzzahl in der Range-basierten for-Schleife.

Seit C++11 kennt die Sprache Lambda-Funktionen – also solche ohne Namen –, die Programmierer gern als Parameter für Funktionen nutzen:

int a= 2000;
int b= 11;
auto sum= std::async( [=]{return a+b;} );
std::cout << sum.get() << std::endl;

Die dritte Zeile verwendet die Lambda-Funktion [=]{return a+b;} als Arbeitspaket für den asynchronen Funktionsaufruf std::async. Das Ergebnis der Funktionsausführung steht mit dem sum.get()-Aufruf zur Verfügung.

Listing 1: Partielle Funktionsanwendung mit std::bind, std::function, auto und Lambda-Funktionen

1  int addMe(int a, int b){ return a+b; } 
2  
3  std::function<int(int)> add1= std::bind(addMe,2000,_1); 
4  auto add2= []{int b){ return addMe(2000,b); }; 
5  auto add3= []{int a){ return addMe(a,11); }; 
6  
7  addMe(2000,11) == add1(11) == add2(11) == add3(2000);  // 2011

Partielle Funktionsanwendung – auch Currying genannt (siehe den Kasten zu Haskell Curry) – erlaubt es, die Argumente einer Funktion teilweise zu binden und damit eine weitere Funktion zu erzeugen, die nur noch die verbleibenden Argumente benötigt. Listing 1 enthält die einfache Funktion addMe, die zwei natürliche Zahlen als Argument verlangt und das Ergebnis der Addition als natürliche Zahl zurückgibt. Die Funktion add1 benötigt hingegen nur noch ein Argument int(int), denn das Funktions-Template std::bind bindet das erste Argument mit 2000. Das zweite Argument _1 ist noch frei. Das Gleiche gilt für die Funktionen add2 und add3, deren erstes beziehungsweise zweites Argument mit 2000 beziehungsweise 11 gebunden wird. Alle Funktionsaufrufe in Zeile 7 geben das gleiche Ergebnis zurück: 2011.

Eingebettet im Imperativen

Listing 2: Faktorial von 5 zur Übersetzungszeit berechnet

1  template <int N>
2  struct Factorial{
3    static int const value= N * Factorial<N-1>::value;
4  };
5  
6  template <>
7  struct Factorial<1>{
8    static int const value = 1;
9  };
10  
11  std::cout << Factorial<5>::value << std::endl;
12  std::cout << 120 << std::endl;

Es wird noch funktionaler. C++ besitzt eine rein funktionale Subsprache, die in die imperative Sprache C++ eingebettet ist: Template-Metaprogrammierung. Hier instanziiert der Compiler die Templates und erzeugt durch diesen Prozess den temporären C++-Sourcecode, den er zusammen mit dem restlichen Quellcode übersetzt und so das ausführbare Programm erzeugt. Da Template-Metaprogrammierung Turing-vollständig ist, entspricht ihre Mächtigkeit der der Programmiersprachen C, C++ oder Java. Listing 2 zeigt den Klassiker: die Faktorial-Funktion als Template-Metaprogramm.

Beispiel einer Template-Metaprogrammierung: die Instanziierung von Factorial<5> (Abb. 1)

Wenn Factorial<5>::value zur Übersetzungszeit instanziiert wird, führt das zur Instanziierung des generischen Templates in Zeile 1 mit Factorial<5>. Factorial<5> instanziiert in Zeile 3 Factorial<4>::value im Ausdruck N * Factorial<N-1>::value. Diese Rekursion endet, wenn der Compiler Factorial<1>::value benötigt. In diesem Fall schlägt das vollständig spezialisierte Klassen-Template in Zeile 6 zu und gibt den Wert 1 zurück. Abbildung 1 stellt den Template-Instanziierungsprozess exemplarisch dar.

Der Auszug der Objektdatei zu factorial<5>::value zeigt den vom Compiler berechneten Hex-Wert (Abb. 2).

Ein genauerer Blick in die Objektdatei in Abbildung 2 bringt die Magie der Template-Metaprogrammierung auf den Punkt. Der Compiler berechnet den Hex-Wert von Factorial<5>::value (0x78), sodass er zur Laufzeit des Programms bereits vorliegt.

C++ bietet viele Techniken aus der funktionalen Programmierung an. Da drängt sich natürlich die Frage auf, was sich hinter dem Begriff der funktionalen Programmierung verbirgt.

Funktionale Programmierung: Die Definition