It is a quirk of C++ that almost all mathematical operations convert their arguments to int
.
Here is a sketch of a non-widening %mod%
operator:
template<class T, class U,class=void> struct smallest{using type=T;};
template<class T, class U>
struct smallest<T,U,std::enable_if_t<(sizeof(T)>sizeof(U))>>{using type=U;};
template<class T,class U>using smallest_t=typename smallest<T,U>::type;
constexpr struct mod_t {} mod;
template<class LHS>struct half_mod { LHS lhs; };
template<class LHS>
constexpr half_mod<std::decay_t<LHS>> operator%( LHS&& lhs, mod_t ) { return {std::forward<LHS>(lhs)}; }
template<class LHS, class RHS>
constexpr smallest_t<LHS, std::decay_t<RHS>> operator%( half_mod<LHS>&& lhs, RHS&& rhs ) {
return std::move(lhs.lhs) % std::forward<RHS>(rhs);
}
The result of a mod b should be the smallest of the two types, as it cannot be larger. Possibly some work should be done for signed/unsigned, but I'll punt and take the first.
So id %mod% 3
ends up being char
.
static_cast<unsigned char>(id % 3)
? – Pryer