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, likeempty_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
CheckBoxconstructor will accept anArgumentPackwith options unrelated toCheckBoxprovided that it doesn't contain anything incompatible with theCheckBox::Paramsexpectations.
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 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.


