Channels ▼
RSS

Tools

Pure C++ Options Classes


A Solution with Effectively Variadic C++98 Macros

Happily the Boost Preprocessor Library, some truly amazing and impressive code written by Paul Mensonides, can make the C++ preprocessor, even the severely limited C++98 preprocessor, sing, dance, compose sonnets, and in general almost anything, it's almost unbelievable!, including that it supports a limited form of variadic macros for C++98 -- so that options can be added or removed freely.

However, since this re-introduces a dependency on Boost I chose to put the Boost'ed macro in a separate header [options_boosted.h], which in turn includes [options.h]. If one already uses Boost then [options_boosted.h] provides convenience at little or no extra cost. If one doesn't already use Boost then the [options.h] n-macros do work.

Using [options_boosted.h], instead of writing


// "manual n" macro from [options.h]
CPPX_DEFINE_3OPTIONCLASS( Params, CPPX_OPTIONS_NO_BASE
    , hTextAlign , int , 0
    , vTextAlign , int , 0
    , buttonPlacement , int , 0
     )

you use a slightly different syntax and write


// "automatic n" macro from [options_boosted.h]
CPPX_DEFINE_OPTIONCLASS( Params, CPPX_OPTIONS_NO_BASE,
    ( hTextAlign, int, 0 )
    ( vTextAlign, int, 0 )
    ( buttonPlacement, int, 0 )
    )

with no n in sight, and no need to count anything, generating the same code.

CPPX_DEFINE_OPTIONCLASS supports from 1 to 11 options, inclusive. The limit of 11 is due to a limit of 12 directly specified types in the non-Boost typelist used internally. There is no way to specify "no options" via this macro, because a Boost Preprocessor Library sequence -- the list of horizontal parentheses above -- can't be empty.

How It Works: Tricky Recursive Macros and Templates

The Boost Preprocessor Library macros include functionality for iterating over simple sequences like (a)(b)(c) at preprocessing time, with a specified macro as a kind of callback -- and yes, I'm talking about C++98 macros that iterate, iteration at preprocessing time. However, the Boost Preprocessor Library iteration macros and other sequence handling macros fail when the sequence is a sequence of tuples like (a,int,1)(b,int,2)(c,int,3), as in CPPX_DEFINE_OPTIONCLASS' third argument. Trying my hand at doing this myself I came to appreciate Paul Mensonides' sheer genius in creating that kind of preprocessor magic in the first place -- I failed.

But I'd seen that BPAL does this, to the extreme. So to create [options_boosted.h] I sort of reverse-engineered some BPAL support macros and vigorously chopped off irrelevant stuff, and thus wrt. BPAL ended up using only apparently undocumented functionality from [boost/parameter/aux_/preprocessor/for_each.hpp]. I used that to convert (a,int,1)(b,int,2)(c,int,3) to ((a,int,1))((b,int,2))((c,int,3)), and then the ordinary Boost Preprocessor Library macros could do the rest.

This additional internal Boost magic, a macro called BOOST_PARAMETER_FOR_EACH_R that can iterate over a sequence of tuples, was written by Daniel Wallin.

Now about the rest, the strictly not-Boost-dependent code.

A CPPX_DEFINE_nOPTIONCLASS invocation expands to code that uses template-based code generation.

The setter overloads are brought in by inheritance from classes templated on the desired setter result type. Each such class defines setter and getter overloads for one option. The particular option is identified by a tag type, as in the BPAL approach.

And this means that also each option's logical data member (a base class sub-object) is necessarily brought in by inheritance, as in the BPAL approach.

However, the setter overloads need to be generated anew for each options class T, to have T& result type, while each option logical data member should only be brought in once. So they need to be treated differently. Each logical option data member name is therefore generated as an ordinary class T_cppx_OptionValue_name, while the generic definition of that option's setter and getter is generated as class template cppx_OptionSetter_, partially specialized on the option's logical data member class.

For example, the earlier example program's macro invocation down in CheckBox,


CPPX_DEFINE_1OPTIONCLASS( Params, AbstractButton::Params
      , isAuto , bool , true
      )

generates, among other things,


class Params_cppx_OptionValue_isAuto
      : public ::progrock::cppx::options::Value_<
           bool,
           struct cppx_UniqueIdTypeFor_Params_isAuto
           >
{
      typedef ::progrock::cppx::options::Value_<
         bool,
        struct cppx_UniqueIdTypeFor_Params_isAuto
        > Base;
public:
        Params_cppx_OptionValue_isAuto()
            : Base( true )
       {}
       Params_cppx_OptionValue_isAuto( bool const& v )
            : Base( v )
      {}
};
template< class OptionValue, class SetterResult, class Base >
class cppx_OptionSetter_;
template< class SetterResult, class Base >
class cppx_OptionSetter_< Params_cppx_OptionValue_isAuto, SetterResult, Base >
      : public Base
{
public:
     bool const& isAuto() const
    {
         return Params_cppx_OptionValue_isAuto::value();
    }
   SetterResult& isAuto( bool const& value )
   {
         Params_cppx_OptionValue_isAuto::setValue( value );
         return ::progrock::cppx::downcast< SetterResult >( *this );
    }
    SetterResult& set_isAuto( bool const& value )
   {
         return isAuto( value );
    }
};

The general inheritance tree assembling these two pieces per option is shown in Figure 1.

[Click image to view at full size]
Figure 1: General inheritance tree.

For each options class T the T-specific inheritance -- of T's base class, appropriate setter overloads for T's own options, and T's own logical option data members -- is accomplished by a generated class template T_cppx_InheritTemplatedSetters_:


template< class SetterResult, class TopBase >
class Params_cppx_InheritTemplatedSetters_
     : public ::progrock::cppx::options::InheritTemplatedSetters_<
           Params_cppx_OptionTypes,
           SetterResult,
          AbstractButton::Params_cppx_InheritTemplatedSetters_<
                SetterResult, TopBase
                >,
         cppx_OptionSetter_
        >
{};

T_cppx_InheritTemplatedSetters_ delegates the main inheritance work to ::progrock::cppx::options::InheritTemplatedSetters_ (static code, not generated by the macro), asking that general InheritTemplatedSetters_ class template to bring in T's base class U's U_cppx_InheritTemplatedSetters_, which in turn will delegate its work back to InheritTemplatedSetters_ asking it to bring in U's base class V, and so on, in a back and forth indirect recursion.

The base case for that particular recursion is


namespace progrock{ namespace cppx{ namespace options {
       class NoBase {};
       template< class SetterResult, class TopBase >
       class NoBase_cppx_InheritTemplatedSetters_: public TopBase {};
} } } // namespace progrock::cppx::options

but as you can see, while it stops recursing up the base class chain to bring in setter overloads, hurray, it still itself inherits from TopBase. What's that? Well, it's the option values for the bottom options class T and all its options class bases.

Deferring consideration of TopBase, the general InheritTemplatedSetters_:


namespace progrock{ namespace cppx{ namespace options {
      template<
          class OptionTypes,
         class SetterResult,
         class TopBase,
         template< class, class, class > class OptionSetter_
        >
   class InheritTemplatedSetters_;
        template<
       class SetterResult,
       class TopBase,
       template< class, class, class > class OptionSetter_
       >
  class InheritTemplatedSetters_<
       cppx::EmptyTypelist, SetterResult, TopBase, OptionSetter_
      >
      : public TopBase
   {};
  template<
     class OptionTypes,
     class SetterResult,
     class TopBase,
     template< class, class, class > class OptionSetter_
     >
 class InheritTemplatedSetters_
    : public OptionSetter_<
    typename cppx::HeadOf< OptionTypes >::T,
    SetterResult,
    InheritTemplatedSetters_<
       typename cppx::TailOf< OptionTypes >::T,
       SetterResult,
       TopBase,
       OptionSetter_
       >
>
{};
} } } // namespace progrock::cppx::options

At this point you see some glimpses of the non-Boost typelist facility, the HeadOf and TailOf class templates from [typelist.h]. It's very limited but sufficient for purposes like this. If you're unfamiliar with typelists you may just study that header, or learn about them from e.g. Andrei Alexandrescu's Modern C++ Design, which is the book that taught me about them -- apart from The C Programming Language it's the single must-have C++ book.

Here OptionSetter_ is a template as template parameter. Depending on the InheritTemplatedSetters_ specialization it can be different templates; each options class can have its own with partial specializations for each option. For example, the inheritance above can be of CheckBox::cppx_OptionSetter_< ..._isAuto, ... >, as shown earlier, where the chain to inherit further from is passed to the OptionSetter_.

Going down to the more concrete, InheritTemplatedSetters_ is instantiated from each options class' T_cppx_InheritTemplatedSetters_ class template, as shown,


template< class SetterResult, class TopBase >
class Params_cppx_InheritTemplatedSetters_
    : public ::progrock::cppx::options::InheritTemplatedSetters_<
        Params_cppx_OptionTypes,
        SetterResult,
        AbstractButton::Params_cppx_InheritTemplatedSetters_< SetterResult, TopBase >,
       cppx_OptionSetter_
      >
{};

which in turn is instantiated from the concrete options class T, like


class Params
    : public Params_cppx_InheritTemplatedSetters_<
       Params,
       Params_cppx_Values
      >
{};

Here the Params_cppx_Values is macro-generated class that assembles the options (via inheritance delegated to the typelist facility's InheritAllOf),


class Params_cppx_Values
     : public AbstractButton::Params
     , public ::progrock::cppx::InheritAllOf<
            Params_cppx_OptionTypes
            >
{};

where Params_cppx_OptionTypes is macro-generated typelist that specifies the options to assemble,


typedef ::progrock::cppx::Typelist<
   Params_cppx_OptionValue_isAuto
       >::T Params_cppx_OptionTypes;


And that's about all, sans some details.

Since I myself find this code difficult to grok it is perhaps not all clear to you! It's a bit intricate, but that's TMP (Template Metaprogramming).

Conclusion

The code supplied with this article has been tested in Windows XP with the MinGW g++ 3.4.5, MSVC 7.1 and MSVC 9.0 C++ compilers, using Boost library versions 1.35 and 1.42 for testing of [options_boosted.h].

I've not included license comments but the Boost license can be assumed, which essentially means that you can use it freely even in commercial applications. Some earlier versions of the code were posted to the [comp.lang.c++] Usenet group. For the automated setters generation approach -- at least for my take on it here -- the generated code is complex, and the supporting fixed code is complex. And much of the fixed code is necessarily in macros, which is not maintainance friendly. And so it's probably not very useful as a general pattern for creating similar things.

Happily the result, like the [options.h] macros, can be very simple to use, as shown, providing clear and simple client code without any visible implementation details, enabling you to define class hierarchies with constructors that have optional, strictly typed named actual arguments -- for example for GUI widget wrappers. Or, for that matter, just ordinary functions with optional arguments. Also, the [options_boosted.h] header may inspire you to create similar effectively variadic C+ +98 macros (although you may of course opt for C99/C++0x variadic macros).

Since it's so easy to use there's IMO no need to generate options classes via scripts, complicating and slowing down the build process. Unless there are special requirements it can all be done in pure C++.

Acknowledgments

I would especially like to thank James Kanze for brainstorming and critique in a discussion of these ideas in the [comp.lang.c++] Usenet group, some long time ago.


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