Say I wanted to write a generic class that maintains an integer that always stays between two values. Something like this:
template<int Lower, int Upper>
class MyInt {
private:
int m_int;
public:
// Constructors, operators...
};
The class invariant is that Lower <= m_int <= Upper
.
Of course MyInt should have all the usual operations that integers tend to have, like assignment and arithmetic operators. MyInt would throw if an operation were to leave it in a state that breaks its invariant. In many cases, however, this should be compile-time detectable. Consider this example code:
int foo = 500;
constexpr int const bar = 500;
MyInt<0,100> a = 15; // OK
MyInt<0,100> b = foo; // Throws at runtime
MyInt<0,100> c = 500; // Compile error?
MyInt<0,100> d = bar; // Compile error?
MyInt<0,100> f = std::integral_constant<int, 500>; // Compile error
For std::integral_constant
, writing the appropriate constructor is straight-forward. But is it possible to compile-time-detect that a
is within range and c
and d
aren't? The assigned values are either literals known at compile time or constexpr constants.
I have tried SFINAE-ing around and whatnot, but I could not find a way to go from value-semantics to template arguments, even for these cases where (I claim) the values are clearly compile-time constants.
Is it true for C++17 that the language does not provide the tools required to implement this? If yes, is this going to change with the upcoming C++20? In case what I'm looking for is impossible, what are the reasons for this? My interest is entirely educational, so I would be interested if I'm missing something (like literals not actually being compile-time constants or something).
Note: I am aware that case f
can be made less ugly by introducing a custom literal suffix and requiring the user to type something like this:
MyInt<0,100> g = 15_constint; // OK
MyInt<0,100> h = 500_constint; // Compile error
I am also aware of the possibility to provide an interface like this:
MyInt<0,100> i;
i.assign<500>(); // Compile error
However both these workarounds come with a certain typing overhead and are not necessarily idiomatic C++.