Die Boost C++ Bibliotheken

Kapitel 53. Boost.Parameter

Boost.Parameter macht es möglich, Parameter als Name/Wert-Paare zu übergeben. Dabei werden nicht nur Funktionsparameter unterstützt, sondern auch Template-Parameter. Die Bibliothek bietet sich besonders dann an, wenn lange Parameterlisten verwendet werden und es schwierig ist, sich die Reihenfolge und Bedeutung der Parameter zu merken. Name/Wert-Paare erlauben es, Parameter in einer beliebigen Reihenfolge zu übergeben. Da zu jedem Wert der Name des Parameters angegeben wird, ist außerdem die Bedeutung jedes Parameters im Code offensichtlich.

Beispiel 53.1. Funktionsparameter als Name/Wert-Paare
#include <boost/parameter.hpp>
#include <string>
#include <iostream>
#include <ios>

BOOST_PARAMETER_NAME(a)
BOOST_PARAMETER_NAME(b)
BOOST_PARAMETER_NAME(c)
BOOST_PARAMETER_NAME(d)
BOOST_PARAMETER_NAME(e)

BOOST_PARAMETER_FUNCTION(
  (void),
  complicated,
  tag,
  (required
    (a, (int))
    (b, (char))
    (c, (double))
    (d, (std::string))
    (e, *)
  )
)
{
  std::cout.setf(std::ios::boolalpha);
  std::cout << a << '\n';
  std::cout << b << '\n';
  std::cout << c << '\n';
  std::cout << d << '\n';
  std::cout << e << '\n';
}

int main()
{
  complicated(_c = 3.14, _a = 1, _d = "Boost", _b = 'B', _e = true);
}

Beispiel 53.1 definiert eine Funktion complicated(), die fünf Parameter erwartet. Die Parameter sollen in einer beliebigen Reihenfolge übergeben werden können. Boost.Parameter bietet hierfür ein Makro namens BOOST_PARAMETER_FUNCTION an, um eine derartige Funktion zu definieren.

Bevor BOOST_PARAMETER_FUNCTION verwendet werden kann, müssen die Parameter definiert werden, die für die Name/Wert-Paare verwendet werden sollen. Hierfür wird auf das Makro BOOST_PARAMETER_NAME zugegriffen. Sie übergeben diesem Makro lediglich einen Parameternamen. Im Beispiel wird BOOST_PARAMETER_NAME fünfmal verwendet, um die Parameternamen a, b, c, d und e zu definieren.

Beachten Sie, dass die Parameternamen automatisch in einem Namensraum tag definiert werden. Das soll Überschneidungen mit gleichnamigen Definitionen in einem Programm verhindern.

Nachdem die Parameternamen definiert wurden, wird auf BOOST_PARAMETER_FUNCTION zugegriffen, um eine Funktion complicated() zu definieren. Als erster Parameter wird der Typ des Rückgabewerts angegeben. Im Beispiel ist dies void. Beachten Sie, dass der Typ geklammert werden muss – der erste Parameter lautet (void).

Als zweiter Parameter wird der Name der zu definierenden Funktion angegeben. Der dritte Parameter ist der Namensraum, in dem die Parameternamen definiert sind. Im vierten Parameter wird auf die Parameternamen zugegriffen, um sie näher zu spezifizieren.

Im Beispiel 53.1 beginnt der vierte Parameter mit required. Aus Sicht von Boost.Parameter handelt es sich dabei um ein Schlüsselwort, das angibt, dass die folgenden Parameter bei einem Aufruf der Funktion angegeben werden müssen.

Hinter required können ein oder mehrere Paare angegeben werden, deren erster Wert ein Parametername und deren zweiter Wert ein Typ ist. Auch hier ist zu beachten, dass der Typ geklammert werden muss.

Für die Parameternamen a, b, c und d wird auf verschiedene Typen zugegriffen. So kann zum Beispiel a verwendet werden, um einen int-Wert an complicated() zu übergeben. Für e ist kein Typ, sondern ein Sternchen angegeben. Boost.Parameter interpretiert dieses Sternchen dahingehend, dass jeder beliebige Typ erlaubt ist. e entspricht einem Template-Parameter.

Nachdem die verschiedenen Parameter an BOOST_PARAMETER_FUNCTION übergeben wurden, folgt der Funktionsblock. Dazu wird wie gewohnt auf ein Paar geschweifter Klammern zugegriffen. Im Funktionsblock kann auf die Parameternamen zugegriffen werden. Sie können wie Variablen verwendet werden, die den Typ haben, der ihnen innerhalb von BOOST_PARAMETER_FUNCTION zugewiesen wurde. Im Beispiel 53.1 werden die fünf Parameter auf die Standardausgabe ausgegeben.

Nachdem complicated() definiert wurde, wird die Funktion in main() aufgerufen. Für den Aufruf wird auf die verschiedenen Parameternamen zugegriffen und ihnen ein Wert zugewiesen. Dabei werden Parameter in einer beliebigen Reihenfolge übergeben.

Beachten Sie, dass Parameternamen bei einem Aufruf mit einem Unterstrich beginnen. Boost.Parameter versucht auf diese Weise zu verhindern, dass es zu Namensüberschneidungen kommt, weil andere Variablen möglicherweise wie die Parameter heißen.

Anmerkung

Um Funktionsparameter in C++ als Name/Wert-Paare anzugeben, können Sie auch auf das Named Parameter Idiom zurückgreifen. In diesem Fall ist eine Bibliothek wie Boost.Parameter nicht nötig.

Beispiel 53.2. Optionale Funktionsparameter
#include <boost/parameter.hpp>
#include <string>
#include <iostream>
#include <ios>

BOOST_PARAMETER_NAME(a)
BOOST_PARAMETER_NAME(b)
BOOST_PARAMETER_NAME(c)
BOOST_PARAMETER_NAME(d)
BOOST_PARAMETER_NAME(e)

BOOST_PARAMETER_FUNCTION(
  (void),
  complicated,
  tag,
  (required
    (a, (int))
    (b, (char)))
  (optional
    (c, (double), 3.14)
    (d, (std::string), "Boost")
    (e, *, true))
)
{
  std::cout.setf(std::ios::boolalpha);
  std::cout << a << '\n';
  std::cout << b << '\n';
  std::cout << c << '\n';
  std::cout << d << '\n';
  std::cout << e << '\n';
}

int main()
{
  complicated(_b = 'B', _a = 1);
}

Mit BOOST_PARAMETER_FUNCTION können auch Funktionen mit optionalen Parametern definiert werden. Optionale Parameter können, müssen aber nicht beim Aufruf angegeben werden.

Im Beispiel 53.2 sind die Parameter c, d und e optional. Diese Parameter sind im Makro BOOST_PARAMETER_FUNCTION nicht hinter required, sondern hinter optional angegeben. Für Boost.Parameter ist optional ähnlich wie required ein Schlüsselwort, das optionale Parameter kennzeichnet.

Optionale Parameter werden ähnlich wie erforderliche Parameter definiert: Sie geben einen Parameternamen gefolgt von einem Typ an. Der Typ wird wie bereits gewohnt geklammert. Optionale Parameter erfordern als dritte Angabe einen Standardwert. Optionale Parameter werden auf diesen Standardwert gesetzt, wenn sie bei einem Aufruf nicht angegeben sind.

Beim Aufruf von complicated() werden die Parameter a und b übergegeben. Dies sind die einzig erforderlichen Parameter. Da die Parameter c, d und e nicht angegeben sind, werden sie auf Standardwerte gesetzt.

Boost.Parameter bietet neben BOOST_PARAMETER_FUNCTION weitere Makros an. So können Sie zum Beispiel das Makro BOOST_PARAMETER_MEMBER_FUNCTION verwenden, wenn Sie eine Methode definieren möchten. Mit BOOST_PARAMETER_CONST_MEMBER_FUNCTION können Sie eine konstante Methode definieren.

Beachten Sie, dass Sie mit Boost.Parameter auch Funktionen definieren können, die versuchen, Werte Parametern automatisch zuzuweisen. In diesem Fall müssen beim Aufruf keine Name/Wert-Paare angegeben werden – es reicht, einfach nur Werte anzugeben. Wenn sich die Typen der Werte, auf die Parameter gesetzt werden können, eindeutig unterscheiden, kann Boost.Parameter erkennen, welcher Wert welchem Parameter zugewiesen werden soll. Dies kann jedoch tiefergehende Kenntnisse in der Template Meta-Programmierung erfordern.

Beispiel 53.3. Template-Parameter als Name/Wert-Paare
#include <boost/parameter.hpp>
#include <boost/mpl/placeholders.hpp>
#include <type_traits>
#include <typeinfo>
#include <iostream>

BOOST_PARAMETER_TEMPLATE_KEYWORD(integral_type)
BOOST_PARAMETER_TEMPLATE_KEYWORD(floating_point_type)
BOOST_PARAMETER_TEMPLATE_KEYWORD(any_type)

using namespace boost::parameter;
using boost::mpl::placeholders::_;

typedef parameters<
  required<tag::integral_type, std::is_integral<_>>,
  required<tag::floating_point_type, std::is_floating_point<_>>,
  required<tag::any_type, std::is_object<_>>
> complicated_signature;

template <class A, class B, class C>
class complicated
{
public:
  typedef typename complicated_signature::bind<A, B, C>::type args;
  typedef typename value_type<args, tag::integral_type>::type integral_type;
  typedef typename value_type<args, tag::floating_point_type>::type
    floating_point_type;
  typedef typename value_type<args, tag::any_type>::type any_type;
};

int main()
{
  typedef complicated<floating_point_type<double>, integral_type<int>,
    any_type<bool>> c;
  std::cout << typeid(c::integral_type).name() << '\n';
  std::cout << typeid(c::floating_point_type).name() << '\n';
  std::cout << typeid(c::any_type).name() << '\n';
}

Im Beispiel 53.3 wird Boost.Parameter eingesetzt, um Template-Parameter als Name/Wert-Paare angeben zu können. Ähnlich wie bei Funktionen ist es so möglich, Template-Parameter in einer beliebigen Reihenfolge anzugeben.

Im Beispiel ist eine Klasse complicated definiert, die drei Template-Parameter erwartet. Da die Reihenfolge der Parameter keine Bedeutung hat, heißen sie A, B und C. A, B und C sind nicht die Namen der Parameter, die beim Zugriff auf die Klasse verwendet werden können. Diese werden ähnlich wie bei Funktionen mit einem Makro definiert. Für Template-Parameter heißt das Makro BOOST_PARAMETER_TEMPLATE_KEYWORD. So werden im Beispiel drei Parameternamen integral_type, floating_point_type und any_type definiert.

Nachdem die Parameternamen definiert sind, muss angegeben werden, welche Typen jeweils übergeben werden dürfen. So soll zum Beispiel der Parameter integral_type verwendet werden, um einen Typ wie int oder long zu übergeben, nicht aber einen Typ wie std::string. Dazu wird auf die Klasse boost::parameter::parameters zugegriffen. Diese wird verwendet, um eine Signatur zu erstellen. In der Signatur wird auf die definierten Parameternamen zugegriffen und angeben, welche Typen mit ihnen übergeben werden können.

boost::parameter::parameters ist ein Tuple, das Parameter beschreibt. Erforderliche Parameter werden dabei mit boost::parameter::required gekennzeichnet.

boost::parameter::required erfordert seinerseits zwei Parameter: Die erste Angabe ist der Name eines Parameters, der mit BOOST_PARAMETER_TEMPLATE_KEYWORD definiert wurde. Die zweite Angabe ist die Beschreibung des Typs, auf den der Parameter gesetzt werden darf. So ist zum Beispiel für den Parameternamen integral_type angegeben, dass er auf einen integralen Typ gesetzt werden muss. Diese Bedingung wird mit std::is_integral<_> ausgedrückt. Dabei handelt es sich um eine Lambda-Funktion, die auf Boost.MPL basiert. Diese Bibliothek stellt mit boost::mpl::placeholders::_ einen Platzhalter zur Verfügung. Wenn der Typ, auf den der Parameter integral_type gesetzt ist, anstelle von boost::mpl::placeholders::_ an std::is_integral übergeben wird und das Ergebnis dieser Lambda-Funktion wahr ist, handelt es sich um einen gültigen Typ. Die Bedingungen für die anderen Parameter floating_point_type und any_type sind ähnlich definiert.

Nachdem die Signatur erstellt und unter dem Name complicated_signature definiert wurde, wird innerhalb der Klasse complicated auf sie zugegriffen. Im ersten Schritt wird die Signatur mit complicated_signature::bind an die Template-Parameter A, B und C gebunden. Der neue Typ args stellt die Verbindung dar zwischen Template-Parametern, die beim Zugriff auf complicated angegeben werden, und den Bedingungen, die für diese Template-Parameter gelten. Im zweiten Schritt wird auf args zugegriffen, um die Parameter abzufragen. Dies erfolgt über boost::parameter::value_type. boost::parameter::value_type erhält als ersten Wert args, als zweiten einen Parameternamen. Der entsprechende Typ, der auf diese Weise definiert wird, repräsentiert den Typ, auf den ein Parameter gesetzt ist. So kann im Beispiel über integral_type auf den Typ zugegriffen werden, der mit dem Parameter integral_type als Template-Parameter complicated übergeben wird.

In main() wird auf complicated zugegriffen, um die Klasse zu instanziieren. Der Parameter integral_type wird auf int, floating_point_type auf double und any_type auf bool gesetzt. Die Reihenfolge, in der die Parameter angegeben werden, spielt keine Rolle. Anschließend wird auf die Typdefinitionen integral_type, floating_point_type und any_type zugegriffen, um ihre Werte mit typeid auszugeben. Wenn Sie das Beispiel mit Visual C++ 2013 kompilieren und ausführen, wird Ihnen int, double und bool angezeigt.

Beispiel 53.4. Optionale Template-Parameter
#include <boost/parameter.hpp>
#include <boost/mpl/placeholders.hpp>
#include <type_traits>
#include <typeinfo>
#include <iostream>

BOOST_PARAMETER_TEMPLATE_KEYWORD(integral_type)
BOOST_PARAMETER_TEMPLATE_KEYWORD(floating_point_type)
BOOST_PARAMETER_TEMPLATE_KEYWORD(any_type)

using namespace boost::parameter;
using boost::mpl::placeholders::_;

typedef parameters<
  required<tag::integral_type, std::is_integral<_>>,
  optional<tag::floating_point_type, std::is_floating_point<_>>,
  optional<tag::any_type, std::is_object<_>>
> complicated_signature;

template <class A, class B = void_, class C = void_>
class complicated
{
public:
  typedef typename complicated_signature::bind<A, B, C>::type args;
  typedef typename value_type<args, tag::integral_type>::type integral_type;
  typedef typename value_type<args, tag::floating_point_type, float>::type
    floating_point_type;
  typedef typename value_type<args, tag::any_type, bool>::type any_type;
};

int main()
{
  typedef complicated<floating_point_type<double>, integral_type<short>> c;
  std::cout << typeid(c::integral_type).name() << '\n';
  std::cout << typeid(c::floating_point_type).name() << '\n';
  std::cout << typeid(c::any_type).name() << '\n';
}

Beispiel 53.4 stellt basierend auf dem vorherigen optionale Template-Parameter vor. Dazu wird in der Signatur auf boost::parameter::optional zugegriffen. Außerdem werden die Template-Parameter von complicated, die optional sein sollen, auf boost::parameter::void_ gesetzt. Für optionale Parameter wird boost::parameter::value_type außerdem ein Standardwert übergeben. Dies ist der Typ, auf den Parameter gesetzt werden, wenn sie nicht verwendet werden.

In main() wird wiederum auf complicated zugegriffen. Diesmal werden nur die beiden Parameter integral_type und floating_point_type verwendet. any_type ist optional. Wird das Beispiel mit Visual C++ 2013 kompiliert und ausgeführt, wird für integral_type short, für floating_point_type double und für any_type bool ausgegeben.

Ähnlich wie bei Funktionsparametern kann Boost.Parameter auch bei Template-Parametern eine automatische Erkennung vornehmen. Sie können eine Signatur so definieren, dass eine eindeutige Zuweisung von Typen an Parameter möglich ist. Ähnlich wie bei Funktionsparametern sind auch in diesem Fall tiefergehende Kenntnisse in der Template Meta-Programmierung erforderlich.