Channels ▼
RSS

C/C++

Pure C++ Options Classes


A Boost Parameter Library-based Solution

When confronted with a general problem that you can imagine that there must be a portable solution for, a good place to start looking for a solution is usually the Boost library. And indeed Boost has a named parameters sublibrary, the Boost Parameter Library (BPAL) (by David Abrahams and Daniel Wallin) that at least partially fits the bill. The BPAL is, naturally, based on templating.

With the usual NPI options are represented as named data members, like


struct Options
{
    int a, b, c;
    Options( int aValue = 1, int bValue = 2, int cValue = 3 )
              : a( aValue ), b( bValue ), c( cValue )
    {}
};
void foo()
{
   Options o;
   cout << o.a << o.b << o.c << endl;
}

… but one key idea of the BPAL approach is that instead of named data members one represents each logical option data member as a base class subobject, like


struct A { int value; A( int v = 1 ): value( v ) {} };
struct B { int value; B( int v = 2 ): value( v ) {} };
struct C { int value; C( int v = 3 ): value( v ) {} };
struct Options: A, B, C {};
void foo()
{
    Options o;
    cout << o.A::value << o.B::value << o.C::value << endl;
}

where the two versions of foo have the exact same effect, producing "123". In BPAL the Options is named ArgumentPack, and instead of directly containing option values it contains logical references to the actual arguments of a function call.

With each logical data member represented as base class subobject the logical data member is referenced by specifying its "tag type", like A, B, and C above. This supports sorting the arguments of a function where the arguments are of templated types and specified in an arbitrary order by the caller, because each argument value, or for efficiency a pointer or reference to each argument value, can be copied to the right place in an Options object simply by using that argument's tag type as identification.

And this argument sorting capability in turn supports writing or generating "templated wrappers" of a function so that named actual arguments can be specified in any order.

BPAL provides a number of boilerplate code generation macros for such templated forwarding wrappers. However, since C++98 does not support constructor forwarding the concept of wrapping and forwarding does not play well with C++98 constructors. Hence, in the example code below, for the client code's constructor calls I rely exclusively on the BPAL's support for constructing ArgumentPack instances via the comma operator, which introduces an extra set of parentheses like (( ... )):


// solution_using_boost_named_parameters.cpp
#include <iostream>
#include <boost/parameter.hpp>
typedef boost::parameter::aux::empty_arg_list EmptyArgumentPack;
namespace abstractButton {
    BOOST_PARAMETER_NAME( hTextAlign )
} // namespace abstractButton
struct AbstractButton
{
    int hTextAlign;
    struct Params
   {
         int hTextAlign;
         template< class ArgumentPack >
         Params( ArgumentPack const& args )
               : hTextAlign( args[abstractButton::_hTextAlign | 0] )
        {}
   };
   explicit AbstractButton(
        Params const& params = Params( EmptyArgumentPack() )
        )
       : hTextAlign( params.hTextAlign )
   {}
};
namespace checkBox {
     using namespace abstractButton;
     namespace tag { using namespace abstractButton::tag; }
    BOOST_PARAMETER_NAME( isAuto )
}   // namespace checkBox
struct CheckBox: AbstractButton
{
     bool isAuto;
     struct Params: AbstractButton::Params
     {
           bool isAuto;
           template< class ArgumentPack >
           Params( ArgumentPack const& args )
              : AbstractButton::Params( args )
              , isAuto( args[checkBox::_isAuto | true] )
         {}
   };
   explicit CheckBox(
       Params const& params = Params( EmptyArgumentPack() )
       )
      : AbstractButton( params )
      , isAuto( params.isAuto )
  {}
};
void show( CheckBox const& cb )
{
     using namespace std;
     cout
         << boolalpha
         << "align = " << cb.hTextAlign << ", "
         << "isAuto = " << cb.isAuto << ".\n";
}
int main()
{
      using namespace checkBox;
      CheckBox widget1;
      show( widget1 ); // 0, true (the default values)
      CheckBox    widget2((
                       _hTextAlign = 1
                        ));
      show( widget2 ); // 1, true
      CheckBox widget3((
           _hTextAlign = 1,
           _isAuto = false
           ));
     show( widget3 ); // 1, false
}

Here the nested Params classes serve to (1) centralize the specification of default values; (2) allow custom defaults; and (3) support handling options separately, without having an instance of, for example,CheckBox. I chose this solution because apparently for constructors the default value specification must done where each value is extracted from a BPAL ArgumentPack, using an | operator, instead of more like a declaration. For example, without the Params as unpacking plus default value helper, AbstractButton would need a templated constructor and a separate default constructor, with redundant specification of the default values in each constructor.

It might seem as if this forces a redundant declaration of each named parameter, e.g., AbstractButton::hTextAlign plus AbstractButton::Params::hTextAlign of the same type. But in real code there might not be an AbstractButton::hTextAlign member. Instead that might for example be a state of some underlying API-level widget.

Why the BPAL-based Solution Can Be Undesirable

Like the manual overloading of setters, the BPAL-based approch works. But also like that previous approach it's not ideal; there are some problems:

  • Avalanche-like cryptic diagnostics: results from the heavy templatization.
  • Large library dependency: using the BPAL forces use of the Boost library (sometimes Boost can't be used, e.g. due to company or project policy).
  • Special client code support, "gruff": at least with the solution above, which is about the best that I could do with BPAL, the client code has to introduce extra supporting namespaces; it has to deal directly with BPAL things such as ArgumentPack, some of it, like empty_arg_list, apparently undocumented; and it has to use unconventional BPAL-specific notation such as the | operator.
  • "Ingenious" code: the pattern can be difficult to understand for maintainers.
  • Too lenient type checking: e.g. the CheckBox constructor will accept an ArgumentPack with options unrelated to CheckBox provided that it doesn't contain anything incompatible with the CheckBox::Params expectations.

The ordinary forwarding-wrapper-functions use of BPAL does not suffer from the type checking leniency problem: the wrappers perform checking, and BPAL supplies macros and class templates to construct the necessary information for that checking. However, BPAL's only macro for generating constructor wrappers, called BOOST_PARAMETER_CONSTRUCTOR, assumes and requires a base class constructor as the implementation to forward to. In the example above that would preclude having CheckBox derived from . Functionality for checking whether a general ArgumentPack produced in a call is a subset of the supported arguments, plus the ability to extend declarations of what arguments are supported, could remedy this problem, but alas, it seems that such functionality is not yet present in BPAL.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video