C++: Komposition mit std::bind

von Hubert Schmid vom 2013-09-29

Seit C++11 gibt es in der Standardbibliothek das Funktionstemplate std::bind, mit dem sich Argumente an Funktionen und Funktionsobjekte binden lassen. Das Ergebnis ist ein Funktionsobjekt, das sich einer Variablen vom Typ std::function zuweisen lässt. Das folgende Listing zeigt, wie man damit ein Objekt erhält, das bei jedem Aufruf den Wert 42 zurückgibt.

std::function<int()> find_answer = std::bind(identity, 42); std::cout << "The answer is: " << find_answer() << '\n';

Üblicherweise wird std::bind zusammen mit std::placeholders für die sogenannte partielle Anwendung verwendet. Dabei wird nur ein Teil der Argumente gebunden, so dass ein Funktionsobjekt mit geringerer Parameteranzahl entsteht. Im folgenden Listing wird auf diese Weise das Funktionsobjekt to_celsius konstruiert, das die Temperatur von Kelvin in Grad Celsius umrechnet.

std::function<double(double)> to_celsius = std::bind(plus, std::placeholders::_1, -273.15); std::cout << "Boiling point of water: " << to_celsius(373.15) << " °C\n";

Die Umrechnung von Kelvin nach Grad Celsius ist einfach, weil nur eine Addition notwendig ist. Anders verhält es sich mit Grad Fahrenheit: Dafür wird eine Kombination aus Addition und Multiplikation benötigt. Wie lässt sich diese Aufgabe mit std::bind lösen?

Ganz einfach: Man verschachtelt zwei std::bind ineinander, wie im folgenden Listing zu sehen.

using namespace std::placeholders; std::function<double(double)> to_fahrenheit = std::bind(plus, std::bind(multiplies, _1, 1.8), -459.67); std::cout << "Boiling point of water: " << to_fahrenheit(373.15) << " °F\n";

Auf den ersten Blick sieht das ganz logisch aus. Doch bei genauerem Hinschauen zeigt sich, dass Magie dahinter steckt. Alleinstehend liefert das innere std::bind ein Funktionsobjekt, das mit einem Argument aufgerufen werden kann. Das äußere std::bind bindet plus mit zwei Argumenten ohne einen Platzhalter zu verwenden. Nach dieser Logik würde man also ein vollständig angewandtes Funktionsobjekt erhalten, das beim Aufruf das innere Funktionsobjekt und -459.67 addiert. So kann das also nicht funktionieren.

Tatsächlich passiert Folgendes: Das äußere std::bind erkennt mittels des Type-Traits std::is_bind_expression, dass es sich beim zweiten Argument um das Ergebnis eines inneren std::bind handelt. In diesem Fall wird nicht das Funktionsobjekt als Argument verwendet, sondern es wird bei jedem Aufruf ausgeführt, wobei die Platzhalter des inneren std::bind durch die Argumente des äußeren std::bind ersetzt werden.

In anderen Worten: std::bind unterstützt die Komposition verschachtelter Ausdrücke. Denn genau das möchte man in der Regel haben.

Addendum

In den Beispielen wurden die Funktionsobjekte identity, plus und multiplies verwendet. Die Namen orientieren sich an den entsprechenden Klassen der Standardbibliothek, doch leider lassen sich Letztere nicht so einfach verwenden. Möglicherweise wird sich das mit C++14 bereits ändern. Der Vollständigkeit wegen gebe ich im folgenden Listing exemplarisch die Definition von plus an, die ich für obige Beispiele verwendet habe.

struct { template <typename Lhs, typename Rhs> auto operator()(Lhs&& lhs, Rhs&& rhs) const { return std::forward<Lhs>(lhs) + std::forward<Rhs>(rhs); } } plus;