Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detect compile-time literals and constants

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

like image 774
Drag-On Avatar asked Jun 03 '18 14:06

Drag-On


Video Answer


1 Answers

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.

like image 90
Nicol Bolas Avatar answered Sep 19 '22 13:09

Nicol Bolas