
Hi, Recently I became quite fond of interfaces with named parameters. I developed variety of different solutions with different trade-off and set of features. At first here is couple notes from my experience: 1. In most cases multiargument interface are not in high performance critical areas. 2. In many cases it's constructors. Now lets delve in more detailed discussion on: Design =============== I. Active vs. passive I found that there are two major branched in designs for named parameters: "active" parameters collection and "passive" one. Submitted library is a variation of "passive" solution: one need to call "get" method to retrieve any parameter from collection: template<typename Params> void foo( Params const& p ) { std::string name = p[name]; std::string description = p[descr]; ... } An "active" solutions automatically place parameters in appropriate slots by single 'apply' call: class A { public: template<typename Params> A( Params const& p ) { p.apply( *this ); } }; Even though "active" solutions in some cases provide an interface look more natural. I found that "passive" solution in most cases more flexible and easier to implement. While in both cases one may employ runtime polymorphism, with "passive interfaces it's more natural. Note that parameter may not necessary is just a simple value, it maybe some kind of optional feature and 'apply' method maybe not so trivial and even invoke some functions. All in all both design seems to have heir advantages and I believe we need to provide support for both flavors. II Interfaces a) Call interface and parameters combining IMO with some very rare exclusions (1/2 optional parameter) the named parameter framework shouldn't require/expect multiple overloading of target function with different number of parameters. As well as any "impl" functions. The only acceptable interface is the one with single Params template parameters (in some cases one may provide positional interface as well, though it's rather exclusion case). This is applicable both to active and passive solutions: class A { A() : ... {} template<typename Params> A( Params const& p ) : m_name( p[name] ), m_gulp( p[gulp] ) {} }; This immediately bring a need for automatic arguments combining. There are several ways to do so, depending on requirements (BWT I liked interface presented in some other post with using ',' as combine operator). b) Optional parameter support In case of active solutions there should be an interface to check presence of specific argument. Submitted solution will cause compile time error on access to non-supplied parameter. This may not be always most convenient and may not be the case at all with different implementation (runtime based) c) Default value support I specifically dislike an interfaces provided to support default values. They are unreasonably cryptic (who would remember difference between | and ||; and why | ) but also misleading since default value is actually with access brackets: what is p["abc"]? There are several alternatives. My personal preference is : int name_ = p.is_present<name> ? p[name] : "abc"; // you will need some MP trick to make it compilable or int name_ = p.is_present( name ) ? p[value] : "abc"; // this assumes runtime based implementation Active solutions doesn't have such issue. Whatever is there gets set by apply call. d) Type safety and restrictions I do not understand why wouldn't we keep data type somewhere around keyword type and require one to supply specific type during invocation.This library instead invented special mechanism of imposing restrictions. I do not believe we need that (Also my guess is that 8 out of 10 developers unfamiliar with library will be left spellbound facing this code) e) Mix of named and positional parameters I don't believe it's actually a good idea to support such mix. IMO it just adding unnecessary confusion and possible source of users errors.. Let's say I expect 2 parameters: 1. string name 2. int value. call foo( value = 5, "my_name" ) will fail, while foo( "my_name", value = 5 ) and foo( value = 5 ) won't. Too much chances to make mistake IMO. III. Implementation IMO whatever design you prefer implementation shouldn't as complex and cumbersome. Named parameters support is pretty simple task. This submission presents 1000 lines of tight MP code (and this is without numerous includes). In majority of the cases much more simple and straightforward solution is possible (with some acceptable IMO tradeoff). Following 80 lines (I think it's possible to make it even smaller would I use mpl) should basically present solution with similar tradeoff as submitted library (without default value support, but IMO it would take another 40-60 lines, just save it into keyword struct): // Library Code template<typename NP1,typename NP2> struct named_parameter_combine; template<typename T, int unique_id> struct keyword; template<typename Derived> struct named_parameter_base { template<typename NP> named_parameter_combine<NP,Derived> operator,( NP const& np ) { return named_parameter_combine<NP,Derived>( np, *static_cast<Derived*>(this) ); } }; template<typename T, int unique_id> struct named_parameter : named_parameter_base<named_parameter<T, unique_id> > { static const int id = unique_id; named_parameter( T const& v ) : m_value( v ) {} T const& operator[]( keyword<T,unique_id> const& ) const { return m_value; } T const& m_value; }; template<bool> struct selector; template<typename NP1,typename NP2> struct named_parameter_combine : named_parameter_base<named_parameter_combine<NP1,NP2> > { named_parameter_combine( NP1 const& np1, NP2 const& np2 ) : m_np1( np1 ), m_np2( np2 ) {} template<typename KW> typename KW::data_type const& operator[]( KW const& kw ) const { return selector<KW::id==NP1::id>::_( m_np1, m_np2, kw ); } NP1 const& m_np1; NP2 const& m_np2; }; template<> struct selector<true> { template<typename NP1,typename NP2,typename KW> static typename KW::data_type const& _( NP1 const& np1, NP2 const& np2, KW const& kw ) { return np1[kw]; } }; template<> struct selector<false> { template<typename NP1,typename NP2,typename KW> static typename KW::data_type const& _( NP1 const& np1, NP2 const& np2, KW const& kw ) { return np2[kw]; } }; template<typename T, int unique_id> struct keyword { typedef T data_type; static const int id = unique_id; named_parameter<T,unique_id> operator=( T const& t ) const { return named_parameter<T,unique_id>( t ); } }; //////////////////////////////////////////////////////////////// // Example: namespace test { typedef char const* c_str; keyword<c_str,1> name; keyword <float,2> value; keyword<int,3> index; double value_default() { return 666.222; } int foo( c_str name, float v, int i ) { } template<class Params> int foo(Params const& params) { foo( params[name], params[value], params[index] ); } } int main() { using test::foo; using test::name; using test::value; using test::index; foo(( index = 20, name = "foo", value = 2.5 )); return 0; } Strict type checking on top of it. This code works under gcc 3.4.2. Should work everywhere. As you may guess by now my vote is NO to accept this library. I personally wouldn't be using it in current form and IMO as it stands now it's just another collection of neat MP tricks. I do not have to much comments on docs and tests, other then they seems incomplete. Regards, Gennadiy P.S. Why is the RC branch of mpl is different from HEAD? Doesn't we supposed to fix HEAD and only then propagate to branch?