|
Boost : |
From: David A. Greene (greened_at_[hidden])
Date: 2006-02-14 22:21:16
David Abrahams wrote:
>>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 was concentrating on interface in my message. The macros are
defined like this:
/// NTP support macro to provide key_is<value> flavor
#define BOOST_NTP(key) \
struct key {}; \
template<typename Type> \
class key ## _is : public boost::ntp::param<key, Type> {};
/// NTP support macro to provide key_is<value> flavor for non-type NTPs
#define BOOST_NTP_C(key) \
struct key {}; \
template<typename Type, Type Value> \
class key ## _is : public boost::ntp::param_c<key, Type, Value> {};
It's very close to what you specified. I just find something less
wordy easier to comprehend. I don't know what a0_is<> is, for
example.
> 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?
I punted on nontype template parameters. I don't know how to do this.
I just wrapped everything in mpl::integral_c to turn it into a type.
If you're asking for how to handle a situation like this:
template<int arg1, int arg3, int arg3>
class foo {
// Do something to extract args as named parameters
}
then I don't know. I hadn't thought about it.
>>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.
default_<> is one way the NTP engine discovers whether a passed
parameter is a named template parameter (versus a positional one).
*_is and param<> are the other ways of identifying NTPs. I put
the default initializers in the class declaration so the user
would not have to fill in all of the template parameters every
time. This way they default to whatever value is bound by default_<>.
The only thing that needs to be explicitly specified in this example
is Bufsize, which can be done with either a named or positional
parameter.
>> // 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.
As stated above, the motivation is to let the user know what arguments
are default in the class template interface and which parameters must
be specified, just like with regular old templates.
How is this done in named_parameters?
> Hmm; you're using mpl::vector -- are you doing anything to avoid
> O(N^2) instantiations for each parameter lookup?
:) I knew that wouldn't get past you. I was concentrating on
getting things to work. Optimization was going to be phase II.
>> // 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;
Can you explain a bit about what this means? What does the binding
do, exactly?
>>public:
>>
>> // Get param values
>> typedef typename lookup<Bufsize, params, info>::type BufsizeP;
>
> And this is... a type? What is it, an mpl::int_<> ?
Yep, exactly.
>> 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;
As I said, I like this syntax better than mine. Here's the difference
between our approaches:
In my case, I opted to make "params" a simple sequence of all of the
parameters passed to the template (the formal arguments that participate
in NTP binding) while "info" specifies the mapping of NTP names to
defaults, or in the case of Bufsize, the fact that it doesn't have a
default.
If I understand your interface correctly, "params" holds both the
formal arguments that participate in NTP binding and the NTP names.
Default specification occurs at lookup time.
We've just separated steps a little bit differently. Right now I
don't have a strong preference for either, though I would like
to understand the purpose of parameters<>::bind<> better. What
kind of binding is being done?
> typedef typename parameter::binding<
> params, Elemtype_is<>, int
> >::type ElemtypeP;
>
>
> typedef typename parameter::binding<
> params, Stride_is<>, mpl::int_<1>
> >::type StrideP;
As I understand it, this both specifies defaults and does the
actual mapping between actual arguments and NTP names.
>>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.
Poor choice of words on my part. This is really two different goals:
- Show which NTPs have defaults right in the template interface so
it's clear to the user without having to look at the use of the
binding mechanism.
- Map all unspecified parameters to their defaults, either because
they don't appear in the actual arguments or because the "use_default"
positional parameter appears.
>> - Can use positional notation for binding and named mechanism
>> arbitrarily.
>
> I don't understand that at all.
Yeah, that makes no sense at all. :)
What I was attempting to convey more concisely than I should have is
the algorithm for binding. Here it is, right from the sources, with
additional (hopefully) clarifying comments:
// Match up given params to the key info. Matches are made according
// to the following algorithm:
// 1. Specified parameters are matched to the key info. This
// includes explicit defaults.
That is, all actual arguments passed with *_is get bound as expected.
// 2. Remaining keys are ordered as specified in the key info.
// Remaining unnamed parameters are ordered as specified in
// the param list. Match up each parameter in order with the
// keys.
Any template parameters that aren't of the form *_is (that is,
positional parameters) get bound in the order that they appear
in key_info. The assumption is that the class template developer
will be nice and list them in the same order as in the template
declaration.
So if I have int, long and double actual arguments and foo, bar
and baz are listed in key_info in that order, foo will get bound
to int, bar to long and baz to double.
// 3. All remaining parameters are implicit defaults. Match
// up with remaining unmatched keys.
These parameter names don't appear in the actual arguments and
we've run out of positional parameters. Everything else is
bound to its default.
>> - 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.
Agreed about reusing knowledge, but what do you find not easy,
or less easy? I find some of the named_parameters syntax rather
unwieldy. It's probably a matter of personal taste. These are
complicated problems to solve and sometimes have complicated
solutions.
>>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).
Yes, unnamed parameters are a missing piece of my work.b Can you
give an example of the lambda expressions to specify legal types?
That sounds very useful.
>>>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.
Point taken. I guess I wrote the macros to do pretty much what I
would have expected them to do. They exist to provide a simpler
interface. Their effect would obviously be documented just like
with any other interface. I tend to shy away from macros myself,
but in this case, it really saves a lot of typing and potentially
nasty template syntax.
>>In the latter case, the interface I have allows the user to
>>declare a named template parameter like this:
>>
>>strict Bufsize {};
>
> ^
> :)
Hopefully the point got across. :)
>>The param<> notation at use then tells the NTP engine that this
>>is a named template parameter.
>
> I see no suuch notation.
Sorry, I should have been more complete. If the user doesn't want to
use macros, he can do this:
struct Key1 {};
struct Key2 {};
struct Key3 {};
typedef tester<boost::ntp::param<Key1, long >,
boost::ntp::param<Key2, int >,
boost::ntp::param<Key3, double> > blah;
With macros it would be
typedef tester<Key1_is<long >,
Key2_is<int >,
Key3_is<double> > blech;
>>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.
Sounds good!
>>> 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.
Yes, they are repeated, but the defaults appear with the
specification of which formal arguments participate in
NTP binding.
>>> // 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.
:) What were you thinking of, exactly? It would be helpful to
see an example of how named_parameters uses defaults in the
template declaration to allow the user to avoid specifying
NTP names that should bind to defaults in the actual arguments.
>>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.
Ok, that looks good.
>>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
Haven't had a chance to peek yet.
>>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?
Looking back over my testcases (just one of them! :) ) I see that
I misspoke about how defaults are specified. The class template in
this particular testcase looks like this:
template<typename Param1 = boost::ntp::default_tag,
typename Param2 = boost::ntp::default_tag,
typename Param3 = boost::ntp::default_tag>
class tester {
private:
typedef tester<Param1, Param2, Param3> This;
typedef boost::ntp::key_info<
boost::mpl::vector<boost::ntp::default_c<Key1, long, 1>,
boost::ntp::default_c<Key2, long, 2>,
boost::ntp::default_c<Key3, long, 3> >
> Info;
typedef boost::mpl::vector<Param1, Param2, Param3> Params;
public:
typedef typename boost::ntp::lookup<Key1, Params, Info>::type
Key1Type;
typedef typename boost::ntp::lookup<Key2, Params, Info>::type
Key2Type;
typedef typename boost::ntp::lookup<Key3, Params, Info>::type
Key3Type;
enum {
value1 = Key1Type::value,
value2 = Key2Type::value,
value3 = Key3Type::value,
};
tester(boost::test_toolbox::output_test_stream &out,
const std::string &name) {
out << name << std::endl;
out << "Value1: " << value1 << std::endl;
out << "Value2: " << value2 << std::endl;
out << "Value3: " << value3 << std::endl;
};
};
The bandwidth test example I posted was somewhat out of date. It
still works, but the duplicate defaults are not required. This
looks just like your use of parameter::not_specified.
>>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).
Yes, "positional" is a better term.
>>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.
So how does something like this work:
template<typename A = parameters::unspecified,
typename B = parameters::unspecified,
typename C = parameters::unspecified>
class Foo {
...
};
typedef Foo<int, long, A_is<double> > myfoo;
Is this just not allowed? I would expect B to be bound to int and
C to long.
>>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.
Ok, I think I get it now. I see how it would be quite useful.
I'll see if I can get some time this weekend to download
the parameters library with NTPs and see about modifying
my Perl script to generate tests. No promises I'll have
anything by Monday, though! ;)
-Dave
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk