|
Boost : |
Subject: Re: [boost] [constrained_value] Constrained Value review results
From: Stewart, Robert (Robert.Stewart_at_[hidden])
Date: 2010-10-18 13:53:48
Robert Kawulak wrote:
> > From: Stewart, Robert
>
> > Given that each bound can be found in the four quadrants
> > independent of the other, your bounded_int class template
> > is restrictive.
>
> Of course it is, because it is only meant to be a shortcut in
> one special and very common case. In all other cases "bounded" is the
> one to use. Its default template parameters have been
> designed to simplify the usage in the other common use cases.
Part of the problem in this discussion has been understanding the supported use cases and then how constrained can be applied to them. It is also easy to get mired in the details when one is just trying to understand what the library offers the newcomer.
I suggest that your documentation present a list of the supported use cases and then develop them according to some measure of frequency of use or, perhaps more appropriate, by increasing complexity. IOW, the list should be complete, and allow random access to the various use cases, but for the sequential reader -- the newcomer -- you want to present the idea starting with the simplest ways to use the library and advancing from there.
At the top of such a list are, I think, predicate-based constraints and compile-time ranges. Your first example, with is_even, is fine, except that it, like the rest, should be written from the perspective of being found via random access. That is, don't build one example upon another assuming they are read in order, but rather assume the reader has jumped into them randomly based upon thinking they apply to a current need.
After predicate-based constraints, you should introduce compile-time bounds. The 0-100 percent example is good, but there's too much implementation detail for a tutorial and the phrasing needs help. I suggest something more like the following:
_______________________________________
A common use case is specifying value constraints based upon compile time constants that never change thereafter. For example, a progress value can vary from zero to 100 percent. Any value outside that range is invalid. constrained supports such constraints as can be seen by this verbose and complicated type:
typedef constrained<put line noise here> percent;
To make such constraints easy to specify, the library provides the following, alternative syntax:
typedef bounded_int<int, 0, 100>::type percent;
percent p;
p = 50; // OK
p = 200; // Exception!
For more on how bounded_int works and its relationship to constrained, look here(link).
The bounds for bounded_int are part of the type so they occupy no space and cannot be changed at runtime. Accordingly, the bounds can be accessed at compile time or at runtime:
BOOST_STATIC_ASSERT(percent::constraint_type::upper_type::value == 100);
assert(p.constraint().upper_bound() == 100);
Note: The constrained type must be one of the types allowed as a non-type template parameter, which means integral types and some pointer types).
_______________________________________
(I think that "upper_type" should be "upper_bound_type" because you use "_bound" in most contexts.)
>From there you can provide examples for the types-as-values, bounded objects (within_bounds), and open ranges. For the former and latter cases, your examples explain too little as you assume the reader is familiar with boost::rational.
In the types-as-values example, your explanation should include text to the effect that, "quarter2type and half2type are defined to always return the same bounds, even though the bounds are computed at runtime. Thus, they are a degenerate case of the more general case described here(link)." The link would be to another use case showing how the type's function call operator can provide a value at runtime.
I find your justification of bounded_int weak. You claim it handles a "very common use case," which I don't dispute, but shouldn't the justification be that the syntax using "constrained" is awkward, verbose, or complex? If you agree with that, then there may be other use cases that deserve special treatment (assuming bounded doesn't already handle them neatly). Given a complete list of supported use cases, it would be easier to judge the need for other simplifications.
> > Don't you need an easier way to supply and configure the bounds so
> > each can be placed into any of the four quadrants independently?
>
> Is the current way too complicated after looking at the
> examples in the docs?
Given the organization of the docs, things are still awkward. You start with "constrained," move to "bounded," and finally to "bounded_int" in fairly short order. When I reach the end of the tutorial, I'm left uneasy about which I would need for a given purpose. My suggestion for how to reorganize the tutorial will help with that. If each simplification (bounded, bounded_int, and anything else that may arise) were clearly documented -- not in the tutorial -- WRT to constrained, it would also help. That is, from the tutorial, after reading about bounded_int, for example, a link to more detailed documentation that shows how bounded_int is a specialization of constrained would be helpful. It would explain that constrained is the foundational component and how bounded_int uses it. By putting that text elsewhere, however, the tutorial is simpler to read. (My suggestion that shows the ugly constrained specialization needed for the simple bounded_int<int, 0, 100> would be the right place for such a link. Indeed, I've just gone back now and added it to my earlier suggestion!)
> > bounded simply arranges for the bounds to be of the value
> > type so they can be altered at runtime, right?
>
> By default, yes, but not in general. They may be of any type,
> see the example with bounded-length string, and depending on the type
> used they can or can't be altered at runtime.
This is another use case that should be documented.
> > bounded_int requires compile time constants for both ranges
> > and cannot be changed at runtime.
> > The key characteristic of the latter, to me, is that the
> > bounds are fixed at compile time; they cannot
> > be changed at runtime. However, I get that bounded can
> > also be configured with types that generate an
> > initial value at runtime that cannot be changed thereafter
> > (so they are, effectively of type T const
> > rather than T. Therefore, runtime mutability is not the
> > entire picture for bounded_int.
>
> Yes, this is what I tried to explain. ;-) The key
> functionality of "bounded_int" is making it easier to specify
> fixed bounds that
> can be expressed with a compile-time constant expression. Not
> ANY fixed bounds.
I'm glad I'm starting to make sense of this stuff.
> > The _c suffix, borrowed from MPL, is good for specifying
> > that the bounds are compile time constants.
> > Adding that to "bounded" seems wrong because the bounded's
> > value can still change, within the confines
> > of the bounds. (The same is true of "static" and "ct" as
> > suffixes or prefixes.)
>
> That's right, but so far I don't think we've found better names.
None of the names is good and I haven't thought of one that would work well. That's why I've been suggesting alternative syntaxes that use bounded (or constrained) so that we don't need a name for what's defying a meaningful name.
> > In your example with quarter2type, you show:
> >
> > bounded<rational, quarter2type, half2type>
> >
> > Perhaps the best thing for what you're calling bounded_int is this;
> >
> > bounded<int, bound_c<0>, bound_c<10> >
> >
> > It isn't as succinct as this:
> >
> > bounded_int<int, 0, 10>
>
> Rather:
>
> bounded<int, bound_c<int, 0>, bound_c<int, 10> >
>
> ...which is even less succint and redundant - in the bound_c
> template you would have to specify the type as the first parameter to
> know what is the type of the second one.
Quite right. However, given that bounded_int is only trying to handle the case of both bounds being compile time constants, maybe this would work:
bounded<int, bounds_c<int, 0, 100> >
(If within_bounds were renamed to just "bounds," the two would be parallels.)
That repeats "int," sadly, but it doesn't introduce a new name for the main class template; it reuses bounded.
> Another option is to have something like:
>
> template<intmax_t Value> bounded_c;
>
> Which has two small issues:
>
> - it is usable only for integer constant expressions (no pointers),
> - it is not usable if one wants to use uintmax_t value too
> big to be expressed with intmax_t.
>
> The issues could be overcame by defining several versions of
> the template, like:
>
> bound_c<typename Type, Type Value>
> bound_int_c<intmax_t Value>
> bound_uint_c<uintmax_t Value>
>
> Which may make things more complicated.
That's not bad.
> > but it does eliminate the troubling naming problem and, I
> > think, reduces confusion. Consider:
> >
> > bounded<int, bound_c<0>, big_prime>
>
> Which currently looks like:
>
> bounded<int, mpl::int_<0>, big_prime>
>
> I can't see a big difference. :P
There isn't a big difference, but "mpl::int_" looks daunting to those unused to MPL.
> In general, the idea looks interesting and also consistent
> with the idea of Vicente Botet to specify bounds inclusion like this:
>
> bounded<int, open_c<0>, close_c<100> >
Nice!
> We could have the bounds specifiers and bounds inclusion specifiers:
>
> namespace bound {
>
> struct included; // bound always included in the interval
> struct excluded; // bound always excluded from the interval
> struct optional; // can be included/excluded at runtime
>
> template <typename Type, Type Value, typename
> Inclusion = included>
> struct c; // bound as a compile-time constant expression
>
> template <intmax_t Value, typename Inclusion = included>
> struct int_c; // bound as a compile-time constant int
Can/should this support optional? Giving the initial value using a compile time constant is not unreasonable, but is it wise to provide for that use case? Might it be better to require runtime initialization of runtime changeable bounds?
> template <uintmax_t Value, typename Inclusion = included>
> struct uint_c; // bound as a compile-time constant uint
Ditto
> template <typename Type, typename Inclusion = included>
> struct of_type; // bound mutable at runtime OR
> constant expressed with type
>
> }
>
> This would allow to write:
>
> bounded<int, bound::int_c<-10>, bound::int_c<10> >
> bounded<int, bound::of_type<int>, bound::of_type<int> >
> bounded<rational, bound::of_type<quarter2type>,
> bound::of_type<half2type> >
> bounded<int, bound::int_c<0>, bound::of_type<big_prime> >
> bounded<int, bound::int_c<0, bound::included>,
> bound::of_type<int, bound::excluded> >
>
> Being more explicit it is also unfortunately more verbose in
> most cases, compared to the current syntax:
>
> bounded_int<int, -10, 10>
This is implicitly inclusive of the bounds which restricts its use still further. By contrast, using the following makes the bounds inclusion explicit thereby reducing the chance for confusion:
using bound::int_c;
using bound::excluded;
bounded<int, int_c<-10, excluded>, int_c<10> >
This avoids the need to name what is defying a good name and increases flexibility and clarity while only slightly reducing the brevity. Obviously, I'm transferring the need to know that bounded_int is inclusive of its bounds to int_c's default, but as int_c can be used in multiple contexts, it's a more useful building block and increases the flexibility of using bounded (versus using bounded_int).
> bounded<int>
> bounded<rational, quarter2type, half2type>
> bounded<int, mpl::int_<0>, big_prime>
> bounded<int, mpl::int_<0>, int, throw_exception<>,
> mpl::false_, mpl::true_>
The last of those isn't meaningful at a glance. What do the Booleans mean at the end? One has to count parameters and match them to the arguments. Clearly, one could created named Booleans -- "included" and "excluded" for example -- but it should rather be expressed something like this, given your previous idea:
bounded<int, int_c<0>, of_type<int, excluded> >
> Implementation of the proposed syntax is relatively easy.
> However, it has also its weaknesses and maybe should rather be an
> alternative to than replacement of the current syntax (it
> could be just another template-typedef for bounded, just as
> bounded_int is).
I'm not sure the weaknesses are significant.
> But then having two possible syntaxes to do exactly the
> same things sounds like a bad idea...
I agree with that to a point. You already provide multiple ways of doing things, however: constrained, bounded, and bounded_int. The right way to view that is to make the common easy and the less common possible. That's also why I suggested writing your tutorial beginning with the simple use cases: it illustrates how easy the common use cases are and moves on to the less common use cases which, while not so easy, are still supported.
_____
Rob Stewart robert.stewart_at_[hidden]
Software Engineer, Core Software using std::disclaimer;
Susquehanna International Group, LLP http://www.sig.com
IMPORTANT: The information contained in this email and/or its attachments is confidential. If you are not the intended recipient, please notify the sender immediately by reply and immediately delete this message and all its attachments. Any review, use, reproduction, disclosure or dissemination of this message or any attachment by an unintended recipient is strictly prohibited. Neither this message nor any attachment is intended as or should be construed as an offer, solicitation or recommendation to buy or sell any security or other financial instrument. Neither the sender, his or her employer nor any of their respective affiliates makes any warranties as to the completeness or accuracy of any of the information contained herein or that this message or any of its attachments is free of viruses.
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk