Boost logo

Boost :

From: David Abrahams (dave_at_[hidden])
Date: 2004-11-12 20:54:38


"Thorsten Ottosen" <nesotto_at_[hidden]> writes:

> I think it should not be accepted. My main motivation is that I
> think it promotes an unsound growth in function interfaces.

Like having support for derivation promotes an unsound reliance on
implementation inheritance and support for operators promotes abuse
of operator overloading?

Features don't promote practices.

> I also miss a comparison of other techniques (eg. bgl_named_params,
> named_constructor_pattern)

I don't know about the latter. Never heard of it. But I'm guessing
you're talking about something like:

       f(slew_is(.799), score_is(55))

If so, either it allows arbitrary parameter ordering and aside from
the use of () instead of = its advantages are equivalent to those of
our library, or it does not, and our library offers the advantage of
freeing the user/reader/maintainer from having to remember parameter
order.

> to do the same and an good explanation why this approach is
> superior.

This approach is superior to bgl_named_params in part because it
avoids undue coupling and dependency in a library design. That
coupling is discussed in depth in this excerpt from C++ Template
Metaprogramming (http://www.boost-consulting.com/mplbook):

  f(slew(.799).name("z"));

Here, the expression slew(.799) would build a instance of class
named_params<slew_tag, float, nil_t> having the empty class nil_t as
its only base class, and containing the value .799 as a single data
member. Then, its name member function would be called with "z" to
produce an instance of:

  named_params<
      name_tag, char const[2] // .name("z")
    , named_params<
          slew_tag, double const // slew(.799)
        , nil_t
>
>

having a copy of the instance just described as its only base, and
containing a reference to "z" as its only data member. We could go
into detail about how each tagged value can be extracted from such a
structure, but at this point in the book we're sure your brain is
already working that out for itself, so we leave it as an
exercise. Instead, we'd like to focus on the chosen syntax of the DSL,
and what's required to make it work.

If you think for a moment about it, you'll see that not only do we
need a top-level function for each parameter name (to generate the
initial named_params instance in a chain), but named_params must also
contain a member function for each of the parameter names we might
want to follow it with. After all, we might just as well have written:

  f(slew(.799).score(55));

Since the named parameter interface pays off best when there are many
optional parameters, and because there will probably be some overlap
in the parameter names used by various functions in a given library,
we're going to end up with a lot of coupling in the design. There will
be a single, central named_params definition used for all functions in
the library that use named parameter interfaces. Adding a new
parameter name to a function declared in one header will mean going
back and modifying the definition of named_params, which in turn will
cause the recompilation of every translation unit that uses our named
parameter interface.

While writing this book, we reconsidered the interface used for named
function parameter support. With a little experimentation we
discovered that it's possible to provide the ideal syntax by using
keyword objects with overloaded assignment operators:

  f(slew = .799, name = "z");

Not only is this syntax nicer for users, but adding a new parameter
name is easy for the writer of the library containing f, and it
doesn't cause any coupling.

> Let me elaborate.
>
> 1. Clearly, this is not nice
>
> window* w = new_window("alert", true, true, false, 77, 65);

True, but sometimes it is appropriate. Even then, you only need two
parameters whose order is not obviously dictated by function to
justify the use of a named parameter interface:

      paint(hue = 1071, luminance = 10)

vs.

      paint(1071, 10)

> However, the amount of work needed to code parameters for this is
> quite large.

Really? Did you compare how much work it would be to use
BOOST_NAMED_PARAMS_FUN vs. what you're suggesting below?

> A simple, effective way is just to do it like this:
>
> window* w = new_window("alert");
> w->set_resizable( true );
> w->set_height( 77 );
> ....

How would you apply that idiom to the algorithms of the Boost Graph
Library?

> and you might provide chaining to make it more concise. In addition
> there is no forwarding problems and you might have these functions
> anyway.

What do you mean by "you might have these functions anyway?"
>
> 2. what happens, then, if I don't have an object, but a free-standing
> function?

That's the main use-case of our library!

> a) wrap the function in an object
> b) do as in http://www.boost.org/libs/graph/doc/bgl_named_params.html
> c) provide overloads of functions that pack related parameters together or
> simply add defaults

It looks like you didn't really read through the library docs. You
just use the library in the normal way.

> I must admit I'm a bit sceptical

That's plain! ;-)

> about functions when they start having more than 3-4 arguments;
> there is almost always some abstraction lurking just waiting to be
> done.

Maybe so, but there are plenty of other good arguments for named
parameters.

-- 
Dave Abrahams
Boost Consulting
http://www.boost-consulting.com

Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk