Boost logo

Boost :

From: David Abrahams (dave_at_[hidden])
Date: 2006-02-10 09:55:08


"David A. Greene" <greened_at_[hidden]> writes:

> 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);

I don't know what's under the covers of those macros; we've tried to
make sure that there's a macro-less interface that's at least
reasonably usable, so those would be:

  template <class T = int>
  struct Elemtype_is
    : parameter::template_keyword<a0_is<>, T>
  {};

I guess we don't have a keyword for non-type template parameters yet
but the rest would be like:

  template <std::size_t n = 0>
  struct Bufsize_is
    : parameter::template_nontype_keyword<Bufsize_is<>, std::size_t, n>
  {};

How is your code dealing with the different types of nontype template
parameters, e.g. std::size_t above?

> 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 {

Hm, interesting. What do these default_ things actually do? Isn't all
the information after the first argument just ignored? Same question
as above about non-type template parameters.
 
> // 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;

Yeah, that is unfortunate. What's the point of the first set of
declarations? We delay the the specification of defaults until the
point of extraction. I suppose that has both upsides and downsides.

Hmm; you're using mpl::vector -- are you doing anything to avoid
O(N^2) instantiations for each parameter lookup?

> // Define scope of lookup (some params may not be NTPs)
> typedef boost::mpl::vector<BufsizeOrNTP,
> ElemtypeOrNTP,
> StrideOrNTP,
> IterationsOrNTP> params;

In our case this would be

    typedef typename parameter::parameters<
        Bufsize_is<>, Elemtype_is<>, Stride_is<>, Iterations_is<>
>::bind<BufsizeOrNTP,ElemtypeOrNTP,StrideOrNTP,IterationsOrNTP>::type params;

>
> public:
>
> // Get param values
> typedef typename lookup<Bufsize, params, info>::type BufsizeP;

And this is... a type? What is it, an mpl::int_<> ?

> typedef typename lookup<Elemtype, params, info>::type ElemtypeP;
> typedef typename lookup<Stride, params, info>::type StrideP;
> typedef typename lookup<Iterations, params, info>::type IterationsP;

In our case (assuming we add support for non-type template parameters)
this would be:

    typedef typename parameter::binding<
        params, Bufsize_is<>
>::type BufsizeP;

    typedef typename parameter::binding<
        params, Elemtype_is<>, int
>::type ElemtypeP;

    typedef typename parameter::binding<
        params, Stride_is<>, mpl::int_<1>
>::type StrideP;

>
> My goals with this interface are:
>
> - Allow arbitrary reordering of parameters
>
> - Unspecified parameters use defaults automatically, regardless
> of position of other parameters

I don't know what you mean by "automatically." I understand the second
part of the sentence.

> - Can use positional notation for binding and named mechanism
> arbitrarily.

I don't understand that at all.

> - Ease of use for user (*_is for readability, etc.)
>
> - Ease of use for developer (though see my notes below)

Doesn't look too easy for the developer. And IMO it would be better
for the developer -- who is likely to want to use the parameter
library anyway for function parameters -- to reuse his knowledge.

> 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.

The parameter library now supplies that. Additionally it has support
for "unnamed" parameters (those whose identity can be deduced from the
type passed) and the specification of MPL lambda expressions to
identify the legal range of types for each parameter (with an error
generated if required parameters can't be found).

>> 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.

It does matter. You're introducing declarations and definitions into
the user's code. He needs to know what's going on.

> 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 see no suuch notation.

> 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. ;)

Yes; we could add a macro easily enough.

>> 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.

But it doesn't; the defaults are repeated and thus spread out.

>> // 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.

Haha! If there was one area in which I was prepared to consider that our
interface could be improved on, it was that part.

> 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.

Agreed.

> In your scheme, how does the developer of the template class
> specify defaults in the template parameter list declaration?

He passes parameter::void_. We're thinking of renaming that
parameter::not_specified.

> The user ought to be able to pass only those parameters she
> really wants to change.

Of course; that's supported. See libs/parameter/test/ntp.cpp

> The one thing I don't like about my
> interface is that this specification is redudant with what's
> passed to key_info<>.

Yeah; why bother?

>> 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.

Yes.

> Can the user name all templates except one, put
> the unnamed template anywhere and have it bind correctly?

Yes. But let's call those "positional." In our terminology "unnamed"
means something else (see above).

> 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.

Ours works differently IIRC; positional parameters bind strictly by
position. But I don't see this as being very important, though. Most
languages that allow this sort of thing natively require that all
positional parameters precede the named ones anyway and I don't see
why a user would want to do otherwise.

> 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?

No, please see
http://www.boost.org/libs/python/doc/v2/class.html#class_-spec for an
example. This predates the parameter library; if I can find the time
I'm going to go through Boost.Python and parameter-ize everything.

> 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.

I hope you don't feel your efforts have gone to waste, but I don't
think there's much left to do. From my point-of-view, it was not a
waste at all because it prodded us to get the NTP feature done in
Boost.Parameter. Daniel tells me you wrote over 400 tests for your
library! It might be a great contribution if the logic of those tests
could be reworked to test Boost.Parameter instead. At the very least
we'd discover if we were missing anything important provided in your
work.

-- 
Dave Abrahams
Boost Consulting
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