Funktional in TR1 und C++11

Inhaltsverzeichnis[Anzeigen]

In diesem Artikel geht es weiter mit unserer Reise durch die funktionalen Feature von klassischem, modernem und zukünftigen C++. Heute stoppe ich in der Gegenwart.

 Was hat modernes C++ zu bieten?

timeline.FunktionalTR1Cpp11

2005 wurden mit dem Technical Report 1 (TR1), basierend auf Boost, 13 Bibliotheken als Kandidaten für den zukünftigen C++ Standard vorgeschlagen. 12 davon schafften es in C++11. Darunter sind beiden Funktionen std::bind und std::function. Beide Funktionen arbeiten Hand in Hand. Während es std::bind auf einfache Art ermöglicht, Funktionsobjekte zu erzeugen, nimmt std::function diese temporären Funktionsobjekte an und bindet sie an Namen. Beide Funktionen benötigen die Headerdatei <functional>. Wenn das kein Wink mit dem Zaunpfahl ist? 

std::bind

Mit std::bind lassen sich neue Funktionsobjekte auf verschiedenste Arten erzeugen, denn es erlaubt:

  • die Argumente an beliebige Positionen zu binden,
  • die Reihenfolge der Argumente umzustellen,
  • Platzhalter für Argumente einzuführen,
  • Funktionen nur teilweise zu evaluieren,
  • das resultierende Funktionsobjekt direkt aufzurufen, in den Algorithmen der Standard Template Library (STL) zu verwenden oder in std::function zu speichern.

std::function

std::function als polymorpher Funktions-Wrapper kann beliebige aufrufbare Einheiten annehmen und als Variable speichern. Aufrufbare Einheiten sind alle Einheiten, die sich wie Funktionen anfühlen. Das können Lambda-
Funktionen, Funktionsobjekte und auch Funktionen sein. std::function als polymorpher Funktions-Wrapper ist immer dann notwendig, wenn der Typ einer aufrufbaren Einheit explizit angegeben werden muss. 

Das war genug der Theorie. Die beiden Funktionen unterstützten ganz neue Anwendungsfälle.

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// bindAndFunction.cpp

#include <functional>
#include <iostream>

double divMe(double a, double b){
  return double(a/b);
}

using namespace std::placeholders;

int main(){

  std::cout << std::endl;

  // invoking the function object directly
  std::cout << "1/2.0= " << std::bind(divMe, 1, 2.0)() << std::endl;

  // placeholders for both arguments
  std::function<double(double, double)> myDivBindPlaceholder= std::bind(divMe, _1, _2);
  std::cout << "1/2.0= " << myDivBindPlaceholder(1, 2.0) << std::endl;

  // placeholders for both arguments, swap the arguments
  std::function<double(double, double)> myDivBindPlaceholderSwap= std::bind(divMe, _2, _1);
  std::cout << "1/2.0= " << myDivBindPlaceholderSwap(2.0, 1) << std::endl;

  // placeholder for the first argument
  std::function<double(double)> myDivBind1St= std::bind(divMe, _1, 2.0);
  std::cout<< "1/2.0= " << myDivBind1St(1) << std::endl;

  // placeholder for the second argument
  std::function<double(double)> myDivBind2Nd= std::bind(divMe, 1.0, _1);
  std::cout << "1/2.0= " << myDivBind2Nd(2.0) << std::endl;

  std::cout << std::endl;

}

 

Ein paar Aufrufvariationen der Funktion divMe (Zeile 6 -8) gefällig. Damit ich die einfache Schreibweise  _1, _2 für die Platzhalter std::placeholders::_1, std::placeholders::_2 in dem Sourcecode verwenden kann, mache ich in Zeile 10 den Namensraum std::placeholders bekannt.

In Zeile 17 binde ich in dem Ausdruck std::bind(divMe, 1, 2.0) die Argumente 1 und 2.0 an die Funktion divMe und rufe sie mit dem anschließend runden Klammern auf: (). In Zeile 20 wird das Funktionsobjekt gebunden und in der anschließenden Zeile mit den Argumenten 1 und 2.0 aufgerufen. Eine ähnliche Strategie verfolge ich in den Zeilen 24, 28 und 32. In Zeile 24 vertausche ich die Reihenfolge der Argumente. In Zeile 28 binde ich nur das erste, in Zeile 32 nur das zweite Argument. std::function nimmt jeweils die Funktionsobjekte an. Dabei bezeichnet das Template-Argument wie double(double, double) (Zeile 24) oder double(double) (Zeile 28 und 32) den Typ des aufrufbaren Einheit, die std::function annehmen kann. double(double, double) steht für eine Funktion, die zwei double Argumente annimmt und eine double Wert zurückgibt.

Zum Abschluss noch die Ausgabe des Programms.

 bindAndFunction

Beeindruckt? Ich denke schon. Insbesondere die letzten beiden Beispiele, bei der std::bind als Argument eine Funktion annimmt, die zwei double Argumente erwartet und eine Funktion zurückgibt, die nur noch ein double Argument benötigt, ist besonders spannend. std::bind wertet in diesen beiden Aufrufen nur teilweise seine Argumente aus und verwendet für die nicht evaluierten Argumente Platzhalter. Diese Technik, das eine aufrufbare Einheit teilweise seine Argumente auswerten kann, wird als partial function application bezeichnet. 

Partial function application

Partial function application ist dem aus der funktionalen Programmierung bekannten currying sehr ähnlich. Der Name currying steht für eine Technik, bei der eine Funktion, die mehrere Argumente benötigt, sukzessiv in Funktionen umgewandelt wird, die nur noch ein Argument benötigt. So gibt es in der Programmiersprache Haskell nur Funktionen, die ein Argument annehmen. Das stellt sich natürlich die Frage: Wie kann eine Funktion add, die zwei Argumenten benötigt, in Haskell implementiert werden? Das geschieht implizit. Funktionen in Haskell, die n Argumente benötigen, werden in Funktion transformiert, die eine Funktion zurückgeben, die n-1 Argumente benötigt. Das erste Element wird dabei bereits evaluiert. 

Der Name currying geht auf die Mathematiker Haskell Curry und Moses Schönfinkel zurück. Erster stand mit seinem Nachnamen nicht nur Pate für die Technik currying, sondern mit seinem Vornamen auch für die Programmiersprache Haskell, die uns in den nächsten Artikeln noch öfters beschäftigen wird. Gelegentlich wird currying aber auch schönfinkeln genannt.

Ein Wermutstropfen

Ein Wermutstropfen bleibt. Sowohl std::bind als auch std::function wurden mit C++11 fast überflüssig. std::bind kann mit Lambda-Funktionen, std::function fast immer mit der automatischen Typbleitung mit  auto ersetzt werden. Mit den Erweiterungen der Kernsprache in C++11, lässt sich das Programm daher einfach umschreiben.

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// lambdaAndAuto.cpp

#include <functional>
#include <iostream>

double divMe(double a, double b){
  return double(a/b);
}

using namespace std::placeholders;

int main(){

  std::cout << std::endl;

  // invoking the function object directly
  std::cout << "1/2.0= " << [](int a, int b){ return divMe(a, b); }(1, 2.0) << std::endl;

  // placeholders for both arguments
  auto myDivBindPlaceholder= [](int a, int b){ return divMe(a, b); };
  std::cout << "1/2.0= " << myDivBindPlaceholder(1, 2.0) << std::endl;

  // placeholders for both arguments, swap the arguments
  auto myDivBindPlaceholderSwap= [](int a, int b){ return divMe(b, a); };
  std::cout << "1/2.0= " << myDivBindPlaceholderSwap(2.0, 1) << std::endl;

  // placeholder for the first argument
  auto myDivBind1St= [](int a){ return divMe(a, 2.0); };
  std::cout<< "1/2.0= " << myDivBind1St(1) << std::endl;

  // placeholder for the second argument
  auto myDivBind2Nd= [](int b){ return divMe(1, b); };
  std::cout << "1/2.0= " << myDivBind2Nd(2.0) << std::endl;

  std::cout << std::endl;

}

 

Noch ein paar Worte zu den Lambda-Funktionen. Der Ausdruck [](int a, int b){ return divMe(a, b); }(1, 2.0) definiert eine Lambda-Funktion, die divMe ausführt. Die abschließenden runden Klammern sorgen dafür, dass die Lambda-Funktion direkt aufgerufen wird. Dies gilt nicht für die Lambda-Funktionen in Zeile 28 und 32. Beide Lambda-Funktionen werden erst in den folgenden Zeilen aufgerufen. Das beeindruckende ist, dass die Lambda-Funktionen das erste Argument (Zeile 32) bzw. das zweite Argument (Zeile 28) binden.

Wie geht's weiter?

Der polymorphe Funktions-Wrapper std::function kann fast immer durch auto ersetzt werden. Fast, denn ab und zu ist es aber notwendig, den Typ der aufrufbaren Einheit explizit anzugeben. Das kann nur std::function. Ein typisches Beispiel für solch ein Anwendungsfall ist ein dispatch table, das ich im nächsten Artikel genauer vorstelle.

 

 

 

 

 

 

 

 

title page smalltitle page small Go to Leanpub/cpplibrary "What every professional C++ programmer should know about the C++ standard library".   Hole dir dein E-Book. Unterstütze meinen Blog.

 

Kommentar schreiben


Abonniere den Newsletter (+ pdf Päckchen)

Beiträge-Archiv

Sourcecode

Neuste Kommentare