I think you have a problem with the specification, which, unfortunately, the implementation was not solved automatically.
Once you write: ConstrainedValue(valtype val) : val{val} , you lose hope that you can detect overflow, because the conversion to valtype happens before the code is called. Because if uint_least8_t translates to an unsigned char , which seems to happen in your (and mine) implementation, (uint_least8_t) 257 2 .
To be able to detect overflows, you must use larger integral types in your constructors and operator = methods.
IMHO, you should use the template constructor, operator = and checkval :
template<typename valtype, valtype minval, valtype maxval> class ConstrainedValue { valtype val; template<typename T> static bool lt(valtype v, T other) { if (v <= 0) { if (other >= 0) return true; else return static_cast<long>(v) <= static_cast<long>(other); } else { if (other <= 0) return false; else return static_cast<unsigned long>(v) <= static_cast<unsigned long>(other); } } template <typename T> static bool checkval (T val) { return lt(minval, val) && (! lt(maxval, val)); } public: ConstrainedValue() : val{minval}
Thus, the compiler will automatically select the correct type to avoid early overflow: you use the original type in checkval and use the best of long long and unsigned long long for comparisons, with caution to compare with a signature / unsigned (no compilation warning)!
In fact, lt could be written easier if you accept a possible (harmless) warning about a mismatch with the signature / unsigned:
template<typename T> static bool lt(valtype v, T other) { if (v <= 0) && (other >= 0) return true; else if (v >= 0) && (other <= 0) return false; else return v <= other; } }
A warning may occur if one of valtype and T is signed and the other is unsigned. This is harmless because cases where v and others have opposite signs are explicitly handled, and if both are negative, they must be signed. Thus, this can only happen when it is signed and the other unsigned, but both are positive. In this case, sentence 5 (5 expressions from the standard for the C ++ programming language, Β§ 10) guarantees that the largest type with unsigned priority will be used, which means that it will be correct for positive values. And this avoids the possible useless conversion to unsigned long long .
But there is still a case that I cannot handle properly: an injector. Until you decrypt it, you cannot be sure whether to enter the input value in long long or in unsigned long long (provided that they are the largest possible integral types). The cleanest way I can imagine is to get the value as a string and decode it manually. Since there are many angular cases, I would advise you:
- get it as a string first
- if the first char is minus
- decodes it to long long - else decodes it unsigned long long
It will still give strange results for really big numbers, but this is the best I can:
friend std::istream &operator >> (std::istream& in, ConstrainedValue& v) { std::string hlp; in >> hlp; std::stringstream str(hlp); if (hlp[0] == '-') { long long hlp2; str >> hlp2; assert(checkval(hlp2)); v.val = static_cast<valtype>(hlp2); } else { unsigned long long hlp2; str >> hlp2; assert(checkval(hlp2)); v.val = static_cast<valtype>(hlp2); } return in; }