Robert:
As a caveat and analogy: I want to use this to represent mathematics rather than just bounds-checking. The constrained value manages the subset within an underlying space (represented by the underlying type int, double, etc.). Then an instantiation of this is an element in this set. And my goal is to be able to take an instantiation/element and access information about the underlying set/type itself (e.g. managing bounds for optimizers, domain/range for functors, etc. from only an instance of the type passed in to a generic function). And if you use linear operators on elements of the subset, then it always returns members of the underlying space (which is mathematically correct. integers are a subset of reals which is a linear space with the +,-,/,* operators on it. Divide an integer by an integer and you get an element of the superset)
More questions:
1) Storage the same as underlying type?:::::::
> OK, now it makes a bit more sense. Moreover, I believe it is possible (though
> maybe not trivial) to implement something similar with the current library.
Cool. This would be necessary for me to use the library, so I hope it is possible to have dynamic bounds associated with the type. If this was completed, would the space/storage taken up by an instantiation of the bounded type be the same as the underlying type? In particular, I would be interested in having a vector of bounded double's have the same storage a a vector of bounded doubles (I would be using ublas for example) so that I could potentially pass on a pointer to a C function for some interoperability (I know... hacky but sometimes necessary for all the C scientific computing libraries out there). And for my numerical work, having an extra parameter or two stored for every value in a big matrix would eat up cache/memory too quickly for no value.
If necessary to accommodate consistency of the storage with just the underlying type, perhaps the idea of a bounded value on an instantiation (like it is today for the runtime) vs. a bounded type (modifiable at runtime, but common to all instance) could have a different template... something like:
typedef bounded_type<double, "INTEREST RATE"> interest_rate;
interest_rate::change_bounds(.8, .99);
2) Casting for underlying numeric type?::::::::::
> http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2199.html
Thanks for educating me on this. I find the myvar.value() syntax to be a little rough since my functions would want to be able to use both bounded types and intrinsic types in the same function, but perhaps this could work with an explicit cast which seems reasonable considering the C++ limitation. For example, would something like the following work to ensure consistency in my own usage?:
template<typname T>
void myfunc1(T& t)
{
cout << std::max(0.0, static_cast<double>(t)); //Note a floating point here
}
void myfunc2(T& t)
{
cout << std::max(0, static_cast<int>(t)); //Note integer here.
}
typedef bounded_type<double, "INTEREST RATE"> interest_rate;
interest_rate::change_bounds(.8, .99);
interest_rate r = .95;
myfunc1(r); //works since casting internal
myfunc1(.75); //also works, may be a double to double cast...
myfunc2(r); //Would this fail?
myfunc2(1); //Should work
Note on the last one that it is trying to call max with an integer... My question here is whether this would work? Does the constrained value only have a direct cast to its underlying type, or is there any way to have it cast to any types that the underlying type can cast to? I would guess not for the last case... and this may be where myvar.value() is absolutely necessary? If the casting to the underlying type always works, then I would suggest that the myvar.value() is not the right way to go about things. What we are talking about here really is a cast, like it or not.
3) Bounds including infinity?::::::::
When we are working with bounds, can we use the numeric infinities with both open and closed sets? I am thinking something like:
typedef bounded_type<double, "Risk Aversion"> risk_aversion;
risk_aversion::change_bounds(1, std::numeric_limits<double>::infinity()); //Might want open or closed depending on if function domain is reals or extended reals.
4) Numeric limits traits?::::::::::::
For numeric processing, we also need to have some association with the numeric limit traits. I realize that there is some kind of conceptual connection between some of the traits here and the bounds themselves, but for practical purposes, these traits are too useful to ignore. Conceptually I think of these traits as being information about how the data is stored, rather than bounds themselves.
What would be really useful is if bounded<> and other types would generate traits on their own based on the underlying type... this way, we wouldn't have to create numeric_limits traits for the types ourselves (which is unreasonable for a library user).
So what I am thinking of is;
typedef bounded_type<double, "Risk Aversion"> risk_aversion;
cout << numeric_limits<risk_aversion>::quiet_NaN();
where the numeric_limits<risk_aversion> subclasses directly from numeric_limits<double>..... I don't know enough about generic programming to know how or if this is possible, but this is necessary for generic numeric programming.
5) Testing set inclusion::::::::
While it is nice to have the bounds checking on the () operator, I would also want to be able to ask the type if a value is in the set. For example, in math:
Let Y = integers
Let X = positive integers
y = -1 \in Y
Is y \in X?
In this:
typedef bounded_type<int, "X"> X;
X::change_bounds(0, std::numeric_limits<int>::infinity());
int y = -1;
cout << X::element_of(y); //would return a boolean? what I really would love to say is is X::element_of() or element_of<X>(y) ?
This is a non-failing operation, and would be used all over in my numeric code, especially with wrapping optimizers.
6) No debug only functionality please::::::::::::
On all of the conversations about turning off bounds checking on debug, I would definitely not want this to happen automatically. And a bounds checking failure should be an exception, not a non-recoverable error. I may want to use this with an optimizer that tries to put in values out of bounds, and I would want to catch it and display a message to the console that the optimization failed, then try with a different initial value.
7) Operations consistent with built in C++ numeric types:::::::::::::::::::
You have successfully educated me on why you need to overload the ++, --, +, -, etc. operators yourself and can't just revert to the underlying type. But focusing entirely on intrinsic numeric types double, int, etc.: Will we have complete coverage of the operators that are defined for these types in C++? Will the compiler end up generating EXACTLY the same code if I do a whole bunch of read only operations on a bounded<double> vs. a double? And will the overloading of these operators work for cross-type operations (e.g. multiplying a constrained integer by a constrained double?)? If the answer is no on these, then we probably can't use this library for scientific processing.
8) Multi-bounds::::::::::::::::
In comments such as: http://lists.boost.org/Archives/boost/2008/12/145662.php
I think this is getting away from what I would hope this library can achieve: Associating sets(in the mathematical sense) of allowed values with a C++ type. I see what the predicates are used for, but I wouldn't use them very often. So I don't think we can think of the multi-bounds here being orthogonal constraints, but rather being a set of disconnected intervals themselves. Why not orthogonal constraints as described in this link (which essentially means intersections of subsets)? Try to construct X = (-infinity, 0] union [1,2] union [3,infinity) with intersecting intervals....
I would love to have a list of disconnected intervals in the space of the underlying type that could be parsed through from the . I do not see this as primarily a predicate question. If this is done with iterators, it might be even more flexible. For example, say that you only wanted even numbers and wanted to list out the first 10 intervals of this set... it could return an iterator to a list of intervals. In this case, it wouldn't calculate all of the intervals at once, but when you stepped forward, it would give you the next valid interval (a single value in this case).
Thanks,
Jesse