Boost logo

Boost :

From: Jake Voytko (jakevoytko_at_[hidden])
Date: 2007-07-03 13:15:02


I decided that I wasn't getting anywhere without a case study to help make
my decision, so I decided to model my 2D plot_range() function, with a few
features I'd like to add but haven't yet. I figure there are four different
ways to implement this: monolithic function without Boost.Parameter,
monolithic function with Boost.Parameter, refactored function without
Boost.Parameter, or refactored function with Boost.Parameter

  // human_age is a functor that converts a human object's age into a double
  // multimap<double, human> data;

------------------------

METHOD 1: monolithic function:

  plot_range(my_plot, data.begin(), data.end(),
                  default_functor, human_age,
                  circle,
                  orange, red,
                  3, 10);

This is obviously unacceptable, readability wise. Even though I designed the
interface an hour ago, it takes me a few seconds to realize what everything
is. However, the cost of this function is no more than copying the <double,
double> contents of data. "null" represents the fact that we don't need a
functor to cast a double to a double. There is no easy way to indicate to
the interface that we'd like to use the default value for the first functor
parameter.

-----------------------------

METHOD 2: monolithic function with Boost.Parameter:

  plot_range(my_plot, data.begin(), data.end(),
                  functor2 = human_age,
                  point_style = circle,
                  fill = orange, stroke = red,
                  stroke_width = 3, size = 10);

This is a little better, as far as readability is concerned. The untrained
observer can easily guess what this function is trying to do, and may need
to look in the documentation to see what functor2 is, if they are unfamiliar
with such things. Best of all, we are able to use a default value for
functor1, and we still have roughly the same speed as METHOD 1.

----------------------

METHOD 3: Refactored function

  point_style my_point(circle, orange, red);
  my_point.set_stroke_width(3);
  my_point.set_size(10);

  plot_range(my_plot, data.begin(), data.end(),
          default_functor,
          human_age,
          my_point);

This is clearer, and gives the untrained eye a better idea of what is going
on. However, it does have the downside of needing extra function overhead,
an extra copy for two colors, two integers, and a style. We also still need
to put in a null for the first functor value. Given the infrequency with
which this function will be called, as well as the amount of data that is
being copied from data, I think we can consider this almost negligible.

----------------------------

METHOD 4: Refactored, using Boost.Parameter:

  point_style my_point(circle,
                     stroke=orange, fill=red,
                     stroke_width=3, size=10);

  plot_range(my_plot, data.begin(), data.end(),
           functor2 = human_age,
           style = my_point);

As far as readability is concerned, this is as good as it is going to get.
It still has the added cost of the extra copies that need to be made, but we
don't have to worry about defaults for the conversion functor.

----------------------------------

I'm starting to lean towards 2 or 4 as a reasonable way to implement this
(though if I run into enough resistance, 3 would be my choice). I really
don't like the idea of having the default functor as a placeholder for the
first argument.. it feels kludgy. I may just be mistaken and not know the
obvious way around that problem, and if so, please let me know :).
Boost.Parameter is a part of Boost, so clearly enough people overcame the
difference in syntax to give it a "yes" vote.

If anybody feels strongly about which way would be best, please speak up
soon, *before* I implement my decision :)

Jake


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