C++: Komposition mit std::bind
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;