WrappingInteger (auto-wrapping integral type)

While learning about ring buffers, I got tired of having to manually remember to wrap the read and write index every time I used them.

So, with the help of some folks in the C++ Help discord (https://discord.gg/J5hBe8F), I came up with this:

#include <iostream>
#include <limits>
template<typename IntegralType, IntegralType min, IntegralType max>
struct WrappingInteger
{
    //====================== Usage Requirements ====================// 
    static_assert(std::is_integral<IntegralType>::value, "Integral type required");
    static_assert( min < max, "min must be less than max" );
    static_assert( max-min < std::numeric_limits<IntegralType>::max() / 2, "max - min must be less than half of the maximum allowed value for Integral Type to prevent overflow");
    
    //====================== Prefix & PostFix ====================// 
    WrappingInteger& operator++() { ++val; return wrap(); }
    WrappingInteger  operator++(int) { auto tmp = *this; ++val; wrap(); return tmp; }
    WrappingInteger& operator--() { --val; return wrap();  }
    WrappingInteger  operator--(int) { auto tmp = *this; --val; wrap(); return tmp; }
    
    //====================== Addition Assignment =========================//
    template<typename OtherType>
    WrappingInteger& operator+=(const OtherType& other)
    {
        IntegralType temp = static_cast<IntegralType>(other);
        while( temp > range )
        {
            val += range;
            wrap();
            temp -= range;
        }
        val += temp;
        return wrap();
    }
    
    WrappingInteger& operator+=( const WrappingInteger& other) { val += other.value; return wrap(); }
    
    //====================== Subtraction Assignment =========================//
    template<typename OtherType>
    WrappingInteger& operator-=(const OtherType& other)
    {
        IntegralType temp = static_cast<IntegralType>(other);
        while( temp > range )
        {
            val -= range;
            wrap();
            temp -= range;
        }
        val -= temp;
        return wrap();
    }
    
    WrappingInteger& operator-=(const WrappingInteger& other) { val -= other.val; return wrap(); }
    
    //===============================================//
    ~WrappingInteger() = default;
    
    //======================= Constructors ========================//
    /** allows you to specify a default value, which will be wrapped to remain within the specified range */
    WrappingInteger(IntegralType v = min) : val(v) { wrap(); }
    
    WrappingInteger(const WrappingInteger& other) = default;
    
    template<typename OtherIntegralType>
    WrappingInteger(const OtherIntegralType& otherType) { val = otherType; wrap(); }
    
    //==================== Assignment Operator ===========================//
    WrappingInteger& operator=(const WrappingInteger& other) = default;
    
    template<typename OtherIntegralType>
    WrappingInteger& operator=(const OtherIntegralType& otherType) { val = otherType; return wrap(); }
    
    //==================== Explicit Conversion and Getters ===========================//
    //allows static_cast<int>(WrappedInteger<T, min, max>) and prevents implicit conversion to IntegralType
    explicit operator IntegralType() const { return val; }
    IntegralType value() const { return val; }
    
    //===================== Addition & Subtraction ==========================//
    //these allow for buffer[idx + 3]; and buffer[3 + idx] to be used, where idx is a WrappedInteger
    friend WrappingInteger operator+(WrappingInteger lhs, WrappingInteger rhs) { return lhs += rhs; }
    friend WrappingInteger operator-(WrappingInteger lhs, WrappingInteger rhs) { return lhs -= rhs; }
    
    //===================== Range info ==========================//
    static constexpr auto rangeMin() { return min; }
    static constexpr auto rangeMax() { return max; }
private:
    WrappingInteger& wrap()
    {
        val -= min;//shift everything so the min/max is 0, (max-min)
        while( val < 0 ) { val += range; }
        val %= (range);
        val += min; //shift everything back so min/max is min, max
        return *this;
    }
    IntegralType val = min;
    static constexpr IntegralType range = max - min;
};

Here’s an example usage:

I’m on the fence about the operator IntegralType being explicit or not, as it requires you do things like:

WrappingInteger<int, 2, 10> wi;
std::vector<int> a = {1,2,3,4,5,6};
std::cout << a[static_cast<int>(wi)+1] << std::endl; //prints '4'

but being explicit like that cures the ambiguous errors that result if that function is not marked as explictit and you tried to do a[wi + 1];

Sometimes C++ can make programmers over complicate things a little. Or perhaps I completely misunderstand!! :slight_smile:
What people have done for decades is wrap unsigned integers naturally and shift down the result (>>). Or use i = (i+1) & 2047; or similar, you can use any add values. And adjust their tables to suit power 2 sizes.

my preference goes to

if (++index >= size)
    index -= size;

:slight_smile:

1 Like

Yes, I’ve used that in reverbs which is faster when odd sizes are needed. Especially when memory cache is important.

The problem is that you have to remember to do those tricks. Put your wrapping trick in a class so you don’t have to remember to do that. Then see what else you have to add to your class to make it behave like your primitive type did.

the goal for me is to be able to use this syntax:

auto val = buffer[idx++];

and not have to think about remembering to wrap idx around.

If you’re going to be using this thing in performance critical code and the index is only ever going to be incremented by one then you should use a subtraction, rather than a modulo, for the wrapping.

1 Like

Do you mean subtracting my one for the loop? No-one is suggesting doing a modulo because % has a divide in the process.

No - I just spotted a modulo in the original post.

Yes, I saw that too.
Unfortunately the price for that generic solution is, that you cannot know, if wrap() was called after an increment, or in a case, when val is close to std::numeric_limits<T>::max().

I would probably vote for this one too

while( val > range ) { val -= range; }

but, there is a potential penalty.
Probably one of the cases, where it is too simple to generalise it IMHO