So hey. We all know that we can do this, right?
1 2 3 4 5 6 7 8 9
|
template<typename T, T Lower, T Upper> class RangeCheckedNumber { /* implementation for a class which warns the user if a number of type T goes outside the range of Lower to Upper */ };
typedef RangeCheckedNumber<int, 0, 1> ZeroToOneNumber;
int main(int argc, char** argv)
{
ZeroToOneNumber num; // From zero to 1
return 0;
}
|
Right, no problem. We're making the type T be an int, and there's also two arguments of type T passed, the lower and upper bounds. One would presumably have an implementation that overrides basic math functions in there and warns the user as stated (this is, in fact, what I'm trying to do).
But what happens when we make one minor tweak and use a float instead of an int?
1 2 3 4 5 6 7 8 9
|
template<typename T, T Lower, T Upper> class RangeCheckedNumber { /* implementation for a class which warns the user if a number of type T goes outside the range of Lower to Upper */ };
typedef RangeCheckedNumber<float, 0.0f, 1.0f> ZeroToOneNumber;
int main(int argc, char** argv)
{
ZeroToOneNumber num; // From zero to 1
return 0;
}
|
We get "error: 'float' is not a valid type for a template constant parameter". And that's in line with the standard, which states that only integer types can be used suchly. I understand *why* the standard is like that (aka, the instantiation for RangeCheckedNumber<float, 0.0f, 3.0f> wouldn't necessarily be the same as for RangeCheckedNumber<float, 0.0f, 6.0f / 2.0f>, but I think it's an idiotic decision on their part. That sort of case is rare, the consequences of it are generally minor, and most importantly, anyone who doesn't realize that floating point math has the risk of such problems needs to go back to CS 101.
But I digress.
Obviously floating point math is important with such a class, so we can't just write it off as "can't be done". One solution that came to mind first was passing in the range in the constructor - but that gets absurd if you have to do that *every time* you declare a member of the class (such a class would be pervasive throughout the program). Another solution was to subclass every type. While not as ugly, it's still a horribly ugly solution, every type having to define its own subclass and override the constructor. I also tried using constexpr declarations, but they failed with a bad-grammar error ("X is not a valid template argument for type 'const float' because object 'X' has not external linkage") unless I lied and said that they were externals, and that made me uncomfortable - and it was no better than what I think is the "proper" solution that I found, which is:
1 2 3 4 5 6 7 8 9 10 11 12
|
template<typename T, const T& Lower, const T& Upper> class RangeCheckedNumber { /* implementation for a class which warns the user if a number of type T goes outside the range of Lower to Upper */ };
float lower = 0.0f;
float upper = 1.0f;
typedef RangeCheckedNumber<float, lower, upper> ZeroToOneNumber;
int main(int argc, char** argv)
{
ZeroToOneNumber num; // From zero to 1
return 0;
}
|
Compiles, links, runs, no errors. Great. But here's the problem. What I *really* want to be able to do is declare variables like:
1 2 3 4
|
typedef RangeCheckedNumber<float, 0.0f, 1.0f> ZeroToOneNumber;
typedef RangeCheckedNumber<float, -1.0f, 1.0f> MinusOneToOneNumber;
typedef RangeCheckedNumber<float, 50f, 100f> FiftyToOneHundredNumber;
...
|
But instead, I must do:
1 2 3 4 5 6 7
|
float ZeroToOneLower = 0.0f, ZeroToOneUpper = 1.0f;
typedef RangeCheckedNumber<float, ZeroToOneLower, ZeroToOneUpper> ZeroToOneNumber;
float MinusOneToOneLower = -1.0f, MinusOneToOneUpper = 1.0f;
typedef RangeCheckedNumber<float, MinusOneToOneLower, MinusOneToOneUpper> MinusOneToOneNumber;
float FiftyToOneHundredLower = 50.0f, FiftyToOneHundredUpper = 100.0f;
typedef RangeCheckedNumber<float, FiftyToOneHundredLower, FiftyToOneHundredUpper> FiftyToOneHundredNumber;
...
|
... and so on down the line. Ugly, awkward. But there may be a solution. Is there any sort of std:: functionality that I can use to make the upper and lower bounds inline? Something along the lines of:
1 2 3 4
|
typedef RangeCheckedNumber<float, std::foo(0.0f), std::foo(1.0f)> ZeroToOneNumber;
typedef RangeCheckedNumber<float, std::foo(-1.0f), std::foo(1.0f)> MinusOneToOneNumber;
typedef RangeCheckedNumber<float, std::foo(50f), std::foo(100f)> FiftyToOneHundredNumber;
...
|
Is there something like that which exists? Using C++11 is A-okay in my book. :)