Channels ▼
RSS

C/C++

Pure C++ Options Classes


A GUI widget such as a button or a listbox typically has a great many options, more than can be reasonably specified as individual positional arguments. With a suitable script language that supports named actual arguments, it's easy to specify values for the relevant options when the widget is created, using defaults for the other options. This works in practice because usually only a few options need non-default values. But how can this be done in the spirit of C++ with strict, static type checking?

The Named Parameter Idiom Solution

The named parameter idiom (NPI), discussed in the C++ FAQ, provides a simple solution for any given single class T. Essentially a TOptions class is defined with a setter member function for each supported T option, where each such setter function returns a TOptions& reference to the TOptions object. The setter calls can then be chained, and the resulting option values object passed to a T constructor.

A T instance can then be constructed like this:


T aTObject( TOptions()
    .text( "blah" )
    .width( 120 )
    );

The TOptions class may be named to provide more readable source code, suggesting the higher level purpose of the most common use case, like the FAQ's (Marshall Cline's) example where T is a class File and TOptions is a class named OpenFile:


File f = OpenFile( "foo.txt" )
   .readonly()
   .createIfNotExist()
   .appendWhenWriting()
   .blockSize( 1024 )
   .unbuffered()
   .exclusiveAccess();

But note that in this example OpenFile does not open the file, it only supplies the parameters -- the name OpenFile just indicates the overall effect of this use case. To support other use cases a better or alternative name might be OpenFileParams.

Alternatively a TOptions class can be named systematically as, for example, a nested class T::Params, which in the FAQ's example would be File::Params. Or there can be many option value classes for a given class T; for example, one per T constructor, with each options class name indicating which constructor one invokes; for example, File::OpenParams. And so on.... Many variations are possible, the above only introduces the main idea.

Why NPI Is Not Enough: The Covariant Setters Problem

When T is a class in a class hierarchy chances are that its immediate base class has an options set, and that T's option set should be an extension. For example, when modeling the Windows API's GUI widgets you may have an AbstractButton class with options hTextAlign, vTextAlign, and buttonPlacement, and among others a concrete derived class CheckBox with additional options is3State and isAuto. And then, especially for the base class initializations, it would be nice if CheckBox::Params was derived from AbstractButton::Params; then, for example, the CheckBox constructor can just pass its Params argument up to AbstractButton.

But with direct use of the named parameter idiom that doesn't work.

For since AbstractButton::Params::hTextAlign returns AbstractButton::Params& you can't chain a call to, for instance, CheckBox::Params::isAuto (this is the minimal example involving only one derived class, and with only one option defined per class):


// This code intentionally fails to compile at line 42.
struct AbstractButton
{
    struct Params
    {
       int hTextAlignValue;
       Params() : hTextAlignValue( 0 ) {}
       int hTextAlign() const
       { return hTextAlignValue; }
       Params& hTextAlign( int v )
      { hTextAlignValue = v; return *this; }
   };
   explicit AbstractButton( Params const& = Params() )
   {}
};
struct CheckBox: AbstractButton
{
    struct Params: AbstractButton::Params
    {
       bool isAutoValue;
       Params(): isAutoValue( true ) {}
       bool isAuto() const
      { return isAutoValue; }
      Params& isAuto( bool v )
     { isAutoValue = v; return *this; }
  };
  explicit CheckBox( Params const& params = Params() )
     : AbstractButton( params )
  {}
};
int main()
{
    CheckBox widget( CheckBox::Params()
                       .hTextAlign( 1 ) // OK
                      .isAuto( false ) // Oops! ERROR! Unknown!
                       );
}

Ah, but no problem -- just let the client (calling) code always specify the base class options last, right? Ufortunately that doesn't work either. For then, in the example above, the actual argument will be of static type AbstractButton::Params, and the CheckBox constructor doesn't accept that: it needs a CheckBox::Params argument.


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