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];

