I have a class that wraps an integer into a range of values known only to the compiler (and the developer), the limits are unknown at runtime. The class implements operators such that the limits change and a new value of a new type with the modified limits is returned (this is crucial).
The following code gives an example of the class, implementing the + operator as described (compiles for c++20):
#include <iostream>
using namespace std;
template< int LOWER_, int UPPER_ >
class MyRange final {
public:
constexpr static int LOWER = LOWER_;
constexpr static int UPPER = UPPER_;
template< int, int >
friend class MyRange;
constexpr MyRange(MyRange const &) noexcept = default;
constexpr MyRange(MyRange&&) noexcept = default;
constexpr ~MyRange() {}
template< int VALUE >
requires ( VALUE >= LOWER && VALUE <= UPPER )
static constexpr
MyRange wrap = MyRange( VALUE );
template< class _RHS, int _RHS_LOWER = _RHS::LOWER, int _RHS_UPPER = _RHS::UPPER,
int _RES_LOWER = LOWER + _RHS_LOWER, int _RES_UPPER = UPPER + _RHS_UPPER,
typename _RESULT_T = MyRange<_RES_LOWER, _RES_UPPER> >
friend
_RESULT_T
operator+(MyRange const lhs, _RHS const &rhs) noexcept {
int result = lhs.value + rhs.unwrap();
return construct<_RESULT_T>( result );
}
int unwrap() const noexcept { return value; }
private:
MyRange() = delete;
MyRange& operator=(MyRange const &) = delete;
MyRange& operator=(MyRange&&) = delete;
// this must not be exposed because value has to be checked against limits at compile-time;
// wrap<VALUE> is the public "constructor"
explicit constexpr MyRange(int value) noexcept : value(value) {}
// helper: construct another specialization of MyRange
template< class TO >
static constexpr TO construct(int value) noexcept { return TO(value); }
int const value;
};
How this can be used:
int main() {
auto value = MyRange<5,20>::wrap<8>;
auto another = MyRange<6,10>::wrap<6>;
auto result = value + another;
// 14; limits: 11, 30
cout << result.unwrap() << "; limits: " << decltype(result)::LOWER << ", " << decltype(result)::UPPER << endl;
}
Now I have the following problem. I would like to be able to add integer literals to variables of the range class type. This could be achieved with explicit or implicit conversion, however this would make the limits explode unnecessarily:
using Range = MyRange<5,20>;
auto value = Range::wrap<8>;
auto result = value + Range::wrap<6>;
// 14; limits: 10, 40
cout << result.unwrap() << "; limits: " << decltype(result)::LOWER << ", " << decltype(result)::UPPER << endl;
Of course I could wrap the literal integer explicitly to obtain the desired result:
auto value = MyRange<5,20>::wrap<8>;
auto result = value + MyRange<6,6>::wrap<6>;
// 14; limits: 11, 26
cout << result.unwrap() << "; limits: " << decltype(result)::LOWER << ", " << decltype(result)::UPPER << endl;
But I don't like it. Too much overhead on the user side. I would rather prefer to write something like auto result = value + 6; and the integer literal 6 is converted implicitly to MyRange<6,6>::wrap<6> before it is passed to the operator.
Is this somehow possible at compile-time?
I tried already to use a consteval function with a value argument which is used to create the desired MyRange type, but unfortunately parameters of consteval functions are not constexpr, although the function is guaranteed to be executed at compile-time. All what I would need would be some way to implicitly take the integer literal from the formula (known at compile-time) to create the desired type that uses the value of the literal as template parameters.
No: the type of an expression cannot depend on the value of a literal in it (outside of any template argument or array bound, and with the special exception for
0possibly being a pointer). This is the “constexprfunction parameters” matter all over again: maybe someday, but not now.