c++templatesc++17

C++: custom integer type to represent spatial dimensions and allows only "2" or "3", for example


At many points in my codebase, I use the following pattern:

template <uint8_t dim>
class Example
{
    static_assert((dim == 1 || dim == 2 || dim == 3), "Example objects must be defined in one, two, or three dimensions.");
    // other stuff
};

and, for regular functions,

template <uint8_t dim>
void some_func()
{
    if constexpr (dim < 2 || dim > 3)
        throw std::invalid_argument("some_func() is valid for 2 or 3 dimensions only.")
    // other stuff
}

I want to avoid having to check that the dimensionality is correct everywhere. Instead, I want something like

template <Dim<1,3> dim> // enforces 1 <= dim <= 3
class Example
// etc.

template <Dim dim> // by default enforces 1 <= dim <= 3
class AnotherExample
// etc.

template <Dim<2> dim> // enforces dim == 2
class AnotherAnotherExample
// etc.

template <Dim<2,3> dim> // enforces 2 <= dim <= 3
void some_func()
// etc/

Note that the allowed dimensions are different for different classes and functions, and the dim template argument is not the same everywhere in the codebase.

An obvious solution would be something like

template <uint8_t dim_lower, uint8_t dim_upper>
class Dim
{
    // something
}

but I don't see how I can declare an object of this Dim in a way that it behaves like a uint8_t. I'd have to instead have Dim store a uint8_t member and then overload the () operator or something, and then I can't simply do template <Dim dim>. Maybe there's a way to use using to get what I want, but I don't see how I can incorporate conditions in using.


Solution

  • You used C++20 syntax in the question, so assuming you can use that, you could just constrain your functions

    template<uint8_t x, uint8_t lo, uint8_t hi>
    concept in_range = x >= lo && x < hi;
    
    template<uint8_t dim>
    requires in_range<dim, 0, 42>
    void foo()
    {
    }
    

    If you want a shorthand for common cases

    template<uint8_t x>
    concept dim3d = in_range<x, 0, 3>;
    
    template<uint8_t dim>
    requires dim3d<dim>
    void bar()
    {
    }
    

    If you only have C++17, there's no syntax sugar whatsoever, and you have to convert all of those into std::enable_if_t

    template<uint8_t x, uint8_t lo, uint8_t hi>
    inline constexpr bool in_range_v = x >= lo && x < hi;
    
    template<uint8_t dim, std::enable_if_t<in_range_v<dim, 0, 42>, bool> = false>
    void foo()
    {
    }