In fact, the promotion policy can be eliminated entirely if the underlying type is always made automatic.
Specifically, instead of the current arrangement where all user-visible templates resolve to safe_base<T, Min, Max, PP, EP>, and assuming for a moment that we only support the range of intmax_t, we could have template<intmax_t Min, intmax_t Max, class EP> class safe_range; as a basis and then define safe<T, EP> as safe_range<min(T), max(T), EP>. Unfortunately, due to the need to support uintmax_t, which doesn't fit in intmax_t, we'd need two safe_range types and support for mixed operations, which would complicate things a bit on the implementation side. On the user side though there'd be no need for promotion policies. Although operator~ would no longer be supportable. One would have to explicitly xor with ~0 (of the correct type.)