|
Boost : |
From: David A. Greene (greened_at_[hidden])
Date: 2006-02-09 20:17:35
David Abrahams wrote:
> I think if you look at what we're planning, maybe we can have a
> productive debate about what the best interface is.
I think that's a good idea. Here's what I have currently and I'll
reply to your ideas after that.
A complete example of use, from developer (of some template class)
and user (of that class) perspective. I've cut non-ntp implementations
out to save space.
#include <boost/ntp/ntp.hpp>
#include <boost/mpl/integral_c.hpp>
#include <sys/time.h>
#include <iostream>
// Declare named template parameters
BOOST_NTP (Elemtype);
BOOST_NTP_C(Bufsize);
BOOST_NTP_C(Stride);
BOOST_NTP_C(Iterations);
using namespace boost::ntp;
using boost::mpl::integral_c;
template<typename BufsizeOrNTP,
typename ElemtypeOrNTP = default_<Elemtype, int >,
typename StrideOrNTP = default_c<Stride, int, 1 >,
typename IterationsOrNTP = default_c<Iterations, int, 100000> >
class read_bandwidth_test {
// Register all keys along with defaults (unfortunately redundant
// with template parameter declarations above)
typedef key_info<
boost::mpl::vector<Bufsize,
default_ <Elemtype, int >,
default_c<Stride, int, 1 >,
default_c<Iterations, int, 100000> >
> info;
// Define scope of lookup (some params may not be NTPs)
typedef boost::mpl::vector<BufsizeOrNTP,
ElemtypeOrNTP,
StrideOrNTP,
IterationsOrNTP> params;
public:
// Get param values
typedef typename lookup<Bufsize, params, info>::type BufsizeP;
typedef typename lookup<Elemtype, params, info>::type ElemtypeP;
typedef typename lookup<Stride, params, info>::type StrideP;
typedef typename lookup<Iterations, params, info>::type IterationsP;
static void run(void) {
// Set up and walk through arrays
};
};
// Pentium III 256k cache
#define CACHE_SIZE (256 * 1024)
// Cached test (note out-of-order params)
typedef read_bandwidth_test<Elemtype_is <int >,
Bufsize_is <int, CACHE_SIZE/2>,
use_default_for<Stride >,
use_default_for<Iterations> > cached_test;
// Uncached test
typedef read_bandwidth_test<Elemtype_is <int >,
Bufsize_is <int, CACHE_SIZE*2>,
use_default_for<Stride >,
use_default_for<Iterations> > uncached_test;
// This works too (default for Stride and Iterations)
typedef read_bandwidth_test<Elemtype_is <int >,
Bufsize_is <int, CACHE_SIZE*2> >
uncached_test_2;
// And this (alternate interface, doesn't require BOOST_NTP macros)
typedef read_bandwidth_test<param <Elemtype, int >,
param_c<Bufsize, int, CACHE_SIZE*2> >
uncached_test_3;
// And this (default for Stride and Iterations)
typedef read_bandwidth_test<use_default,
param <Elemtype, int >,
param_c<Bufsize, int, CACHE_SIZE*2> >
uncached_test_4;
// And this (positional + named binding)
typedef read_bandwidth_test<integral_c<int, CACHE_SIZE*2>,
param <Elemtype, int > >
uncached_test_5;
// And this (positional defaults, integral_c bound to Bufsize)
typedef read_bandwidth_test<param <Elemtype, int >,
integral_c<int, CACHE_SIZE*2>,
use_default,
use_default>
uncached_test_6;
template<typename Test>
void run_test(void)
{
// Keep track of time and calculate stats, etc.
}
int main(void)
{
run_test<cached_test>();
run_test<uncached_test>();
run_test<uncached_test_2>();
run_test<uncached_test_3>();
run_test<uncached_test_4>();
run_test<uncached_test_5>();
run_test<uncached_test_6>();
return(0);
}
My goals with this interface are:
- Allow arbitrary reordering of parameters
- Unspecified parameters use defaults automatically, regardless
of position of other parameters
- Can use positional notation for binding and named mechanism
arbitrarily.
- Ease of use for user (*_is for readability, etc.)
- Ease of use for developer (though see my notes below)
In one way or another, I've come across needs for all of these
properties. The ability to arbitrarily reorder things and
mix positional and named parameters is particularly useful
when working with existing code. NTPs can be added
incrementally without too much trouble.
> A user might do something like this:
>
> namespace parameter = boost::parameter;
>
> // Declare a named template parameter wrapper
> template <class T = void>
> struct value_type_is
> : parameter::named_template_parameter<value_type_is<>, T> {}
This is what I use BOOST_NTP for. Of course it doesn't really
matter what the underlying code looks like unless the user doesn't
want to use macros for some reason.
In the latter case, the interface I have allows the user to
declare a named template parameter like this:
strict Bufsize {};
The param<> notation at use then tells the NTP engine that this
is a named template parameter. I prefer the macroized version
myself because you get nice notation like Bufsize_is<> for
free. I stole that from the original iterator_adaptors
library. ;)
> template <class A0, class A1, class A2>
> struct foo
> {
> typedef parameter::parameters<
> value_type_is<> // more parameters listed here
> > foo_parameters;
So far, this looks exactly like my interface.
> typedef typename foo_parameters::bind<A0,A1,A2>::type args;
Here we differ. You use bind<> while I use key_info<>.
I prefer the bind<> notation but I like the fact that key_info<>
keeps parameters and defaults together in one place.
> // Extract the value_type; the default is int
> typedef typename parameter::binding<
> args, value_type_is<>, int
> >::type value_type;
> };
So in my interface the programmer specifies defaults at
declaration time while in yours she does it at lookup time.
I think I like yours better.
I'm trying to think of whether there are advantages of one
over the other. A possible one is that in my scheme the
programmer cannot accidentally specifiy two different default
values for an NTP. In yours one could conceptually have two
typedefs for value_type_is<>, one with a default of int
and another with, say, float. I don't know why anyone would
do this, though. I don't think it's a big deal.
In your scheme, how does the developer of the template class
specify defaults in the template parameter list declaration?
The user ought to be able to pass only those parameters she
really wants to change. The one thing I don't like about my
interface is that this specification is redudant with what's
passed to key_info<>.
> foo<float, .., ..>::type == float
> foo<value_type_is<float>, .., ..>::type == float
I'm not sure what you're trying to convey here. It looks
like you have both positional and named binding, which is
good. Can the user name all templates except one, put
the unnamed template anywhere and have it bind correctly?
In my implementation, the user could specify one named
parameter and three unnamed ones and the unnamed parameters
will bind in the order of declaration, even though
positionally they may not "align" with the declaration.
For example:
read_bandwidth_test<int,
integral_c<int, 1>,
integral_c<int, 100000>,
Bufsize_is<int, CACHE_SIZE*2> >
would bind Elemtype to int, Stride to 1 (mpl constant) and
Iterations to 100000.
> The beauty of this is that it will re-use all of the existing
> capabilities of the library, like positional and unnamed arguments
> (those that can be distinguished based on their properties and so
> don't need to be tagged explicitly).
I still don't completely understand unnamed arguments. Does
my last example above cover what you mean? The first three
parameters are not named but the naming of the last one defines
the binding order for the others.
I'm happy to help with an implementation based on named_parameters
but as before I will need some documentation of internals so I can
understand the code.
-Dave
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk