Detect compile-time literals and constants

  • A+
Category:Languages

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++.

 


In case what I'm looking for is impossible, what are the reasons for this?

Basically it comes down to the fact that the C++ function model does not permit a function to recognize a distinction between a constexpr parameter and a runtime parameter. If a function takes an int, then it takes an int. That function's behavior cannot be different based on whether that int is supplied by a constant expression (like a literal) or a runtime value.

And this is broadly true even for constexpr functions. You can't have a constexpr function which can do compile-time checks on its parameters. The evaluation can be done at compile-time, but doing foo(500); must ultimately behave the same as doing auto b = 500; foo(b);.

This is why the workarounds all involve using tricks to put the constant expression in a template argument, where such recognition is possible. Of course, a template argument can never be a runtime value, so that creates other issues (like having to have multiple functions). And being a template argument is a generally painful thing to work with.

Essentially, what you want requires that a function can declare that a parameter is constexpr (along with overloading rules to allow you to have a non-constexpr version). No such proposal has been voted into C++20. The best that's been done is P1045, which hasn't even been discussed by the committee yet, and has a bunch of holes that need to be filled in. So I wouldn't hold my breath.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: