C++ bounded::integer reference

Class definition

enum class storage_type { fast, least };

template<intmax_t minimum, intmax_t maximum, typename overflow_policy, storage_type storage> class integer;

Member types

overflow_policy_type
The template parameter overflow_policy
underlying_type
The basic C++ integer type that actually holds the data. If storage_type is set to fast, this corresponds with the fast typedefs in cstdint. If storage_type is set to least, this corresponds with the least typedefs (smallest space used).

Constructors

integer() // default constructor
Does not initialize its value, the same as saying int x;. noexcept
constexpr integer(integer_type other)
Sets the value of the new object to the value of the old object. If integer_type is a bounded::integer, it forwards the overflow policy of other into the overflow policy of this object. The constructor is implicit if the range of integer_type fits entirely within the range of the minimum and maximum template parameters, as determined by querying std::numeric_limits. The constructor is explicit if there is overlap in the range of legal values or the overflow policy has the static member variable overflow_is_error set to false. Otherwise, the constructor does not participate in overload resolution. If the argument is an enum type, the same conditions apply to the underlying_type, except that the constructor is never implicit. Throws whatever exceptions are thrown by the overflow policy's member function assignment.
constexpr integer(integer_type other, overflow_policy_type policy)
Same as above, but uses the specified overflow policy instead of the overflow policy of other.
constexpr integer(integer_type other, bounded::non_check_t)
Same as above, but never applies the overflow policy to the value. If the value is out of range, the behavior is undefined. noexcept
constexpr integer(integer_type other, overflow_policy_type policy, bounded::non_check_t)
Combination of the previous two constructors. noexcept

Assignment

auto operator=(integer_type other) & -> integer &
Assigns the value of other to this object. Does not alter the value of this object's overflow_policy. Throws whatever exceptions are thrown by the overflow policy's member function assignment.
auto operator=(integer_type other) volatile & -> integer volatile &
Same as above.
auto unchecked_assignment(integer_type other) & -> integer &
Assigns the value of other to this object. Does not alter or apply the overflow policy. noexcept.
auto unchecked_assignment(integer_type other) volatile & -> integer volatile &
Same as above.

Accessors

constexpr auto value() const -> underlying_type const &
Returns the stored value. noexcept
constexpr auto value() const volatile -> underlying_type const volatile &
Returns the stored value. noexcept
constexpr explicit operator integer_type() const
Converts to whatever integer type was requested. noexcept.
constexpr auto overflow_policy() const -> overflow_policy_type const &
Returns the overflow policy. noexcept.
constexpr auto overflow_policy() const volatile -> overflow_policy_type const volatile &
Returns the overflow policy. noexcept.
constexpr auto overflow_policy() -> overflow_policy_type &
Returns the overflow policy. noexcept.
constexpr auto overflow_policy() volatile -> overflow_policy_type volatile &
Returns the overflow policy. noexcept.

Overflow policy

Requirements

An overflow policy has the following requirements:

Additionally, if the overflow policy is DefaultConstructible, so will the bounded::integer that uses this policy.

Pre-defined policies

null_policy

The null_policy is the most basic policy of all. Its solution to out-of-bounds values is to declare them to be undefined behavior. If the values are used in a constexpr context, this policy does perform out-of-bounds checking, but it does not perform a run-time check.

throw_policy

If an out-of-bounds value is passed to assignment, it throws an exception of type std::range_error.

clamp_policy

If an a value less than minimum is passed to assignment, it returns minimum. If a value greater than maximum is passed to assignment, it returns maximum.

Dynamic Policies

There are three policy adapters defined as well: dynamic_policy, dynamic_min_policy, and dynamic_max_policy. dynamic_min_policy has a value that can be set at run-time that can further restrict the minimum bound beyond that of the static bound. dynamic_max_policy has a value that can be set at run-time that can further restrict the maximum bound beyond that of the static bound. dynamic_policy has both. All dynamic policies have member functions min and max that allow the user to retrieve the value (or assign to it, where appropriate). All of them accept the following template parameters:

template<intmax_t static_minimum, intmax_t static_maximum, typename overflow_policy, storage_type storage = storage_type::fast>

These parameters are passed directly to the bounded::integer type that will hold the run-time bound. If the overflow_policy template parameter is stateful, each dynamic policy has its own copy of that state that is separate from the state of the dynamic bounds.

Reducing boilerplate: basic_policy

There is a template<typename policy> basic_policy; defined to reduce boilerplate for most simple policies. If the policy passed in as a template parameter defines the member variables is_modulo, overflow_is_error and assignment, basic_policy provides all other requirements, including the ability to constrct from any other policy. basic_policy can only be used with stateless policies.

common_policy

Similar to std::common_type, the bounded library has a metafunction common_policy. This can be specialized as long as least one of the types in the specialization is a user-defined policy. It accepts any number of types greater than zero. There is also an alias template, common_policy_t, to mirror std::common_type_t.

For three or more policies passed in, the metafunction is recursively applied, two policies at a time.

common_policy has a nested type using type = T. If common_policy is passed either of its arguments as null_policy, then there is a nested type that is defined as the other type. Otherwise, there is no nested type defined.

In other words, the null_policy 'loses' to any other policy, otherwise, it is a compile-time error to attempt to get the common_policy of mixed policies.

Typedefs

namespace bounded {

template<intmax_t minimum, intmax_t maximum>
using checked_integer = integer<minimum, maximum, throw_policy>;

template<intmax_t minimum, intmax_t maximum>
using clamped_integer = integer<minimum, maximum, clamp_policy>;

template<intmax_t minimum, intmax_t maximum, typename base_overflow_policy = throw_policy>
using dynamic_integer = integer<minimum, maximum, dynamic_policy<minimum, maximum, base_overflow_policy>>;

template<intmax_t minimum, intmax_t maximum, typename base_overflow_policy = throw_policy>
using dynamic_min_integer = integer<minimum, maximum, dynamic_min_policy<minimum, maximum, base_overflow_policy>>;

template<intmax_t minimum, intmax_t maximum, typename base_overflow_policy = throw_policy>
using dynamic_max_integer = integer<minimum, maximum, dynamic_max_policy<minimum, maximum, base_overflow_policy>>;

}	// namespace bounded

Arithmetic operators

All basic arithmetic operators return by value. bounded::integer supports the following arithmetic operators:

unary plus (+a)
The type of the output is the same as a.
unary minus (-a)
The type of the output has the same overflow policy and storage_type as a, but the bounds can now hold the negation of the original values. For instance, if a was between 1 and 10, -a is between -10 and -1.
addition (a + b)
This is only defined for bounded::integer with the same storage_type. The resulting type is able to store any sum of a and b.
subtraction (a - b)
This is only defined for bounded::integer with the same storage_type. The resulting type is able to store any difference of a and b.
multiplication (a * b)
This is only defined for bounded::integer with the same storage_type. The resulting type is able to store any product of a and b.
division (a / b)
This is only defined for bounded::integer with the same storage_type. The resulting type is able to store any quotient of a and b. This fails to compile with a static_assert if the min or max of b is 0.
modulo (a % b)
This is only defined for bounded::integer with the same storage_type. The resulting type is able to store any remainder of a and b. This follows the same rules as built-in types with regards to the sign of the result. The net effect is that the sign of the result is the same as the sign of a. This fails to compile with a static_assert if the min or max of b is 0.
left shift (a << b)
This is only defined for bounded::integer with the same storage_type. The resulting type is able to store any shifted value of a by b. This fails to compile with a static_assert if either a or b can be negative.
right shift (a >> b)
This is only defined for bounded::integer with the same storage_type. The resulting type is able to store any shifted value of a by b. This fails to compile with a static_assert if either a or b can be negative.

Compound assignment operators

All of the above binary operators have an equivalent compound assignment operator (+=, -=, *=, /=, %=, <<=, >>=). These behave the same as first calling the above operator, then calling the assignment operator on the left-hand-side argument.

Comparison operators

bounded::integer supports all of the comparison / relational operators (==, !=, <, >, <=, >=). Any bounded::integer can be compared with any other bounded::integer or any built-in type. Doing so never invokes undefined behavior, nor are there any surprising changes in value. A bounded::integer with a value of -1 will always compare less than any value of std::uintmax_t, for instance.

String conversions

The bounded namespace contains all overloads of to_string that exist for std::to_string (by way of using std::to_string). It also provides overloads for bounded::integer types. The result of calling this function is the same as calling std::to_string on the underlying_type, except that if the underlying_type is a character type, it is first promoted to int. The underlying type can be a character type on many systems where std::uint8_t is just a typedef for unsigned char, for example.

Stream operators

bounded::integer overloads are provided for the insertion (<<) and extraction (>>) operators for a left-hand-side argument of std::basic_ostream. This inputs / outputs the value formatted as an integer. Very large integer values input can invoke undefined behavior, as operator>> first pulls in the value as std::intmax_t and then constructs a bounded::integer from that. This is a temporary limitation of the current implementation.

std::numeric_limits

bounded::integer provides a specialization of std::numeric_limits. It provides all of the required values / functions.

static constexpr bool is_specialized
true
static constexpr bool is_signed
true if the minimum value is less than 0, otherwise false.
static constexpr bool is_integer
true
static constexpr bool is_exact
true
static constexpr bool has_infinity
false
static constexpr bool has_quiet_NaN
false
static constexpr std::float_denorm_style has_denorm
std::denorm_absent
static constexpr bool has_denorm_loss
false
static constexpr std::float_round_style round_style
std::round_toward_zero
static constexpr bool is_iec559
false
static constexpr bool is_bounded
true
static constexpr bool is_modulo
The same as the overflow policy's is_modulo member
static constexpr int radix
2
static constexpr int digits
The number of digits in base 2.
static constexpr int digits10
The number of digits in base 10.
static constexpr int max_digits10
0
static constexpr int min_exponent
0. Not meaningful because this is an integer type.
static constexpr int min_exponent10
0. Not meaningful because this is an integer type.
static constexpr int max_exponent
0. Not meaningful because this is an integer type.
static constexpr int max_exponent10
0. Not meaningful because this is an integer type.
static constexpr bool traps
true if 0 is in the range of possible values and the underlying type traps.
static constexpr bool tinyness_before
false. Not meaningful because this is an integer type.
static constexpr bounded::integer<minimum, minimum, overflow_policy, storage> min()
Returns the minimum value. noexcept. Note that the return type is not the same as the type passed in. This is because it is statically known what the bounds are, and they can be much tighter than returning the exact bounded::integer type.
static constexpr bounded::integer<minimum, minimum, overflow_policy, storage> lowest()
Returns the minimum value. noexcept. Note that the return type is not the same as the type passed in. This is because it is statically known what the bounds are, and they can be much tighter than returning the exact bounded::integer type.
static constexpr bounded::integer<maximum, maximum, overflow_policy, storage> max()
Returns the maximum value. noexcept. Note that the return type is not the same as the type passed in. This is because it is statically known what the bounds are, and they can be much tighter than returning the exact bounded::integer type.
static constexpr implementation defined type epsilon()
Returns some undefined value. Not meaningful because this is an integer type.
static constexpr implementation defined type round_error()
Returns some undefined value. Not meaningful because this is an integer type.
static constexpr implementation defined type infinity()
Returns some undefined value. Not meaningful because this is an integer type.
static constexpr implementation defined type quiet_NaN()
Returns some undefined value. Not meaningful because this is an integer type.
static constexpr implementation defined type signalling_NaN()
Returns some undefined value. Not meaningful because this is an integer type.
static constexpr implementation defined type denorm_min()
Returns some undefined value. Not meaningful because this is an integer type.

min / max

bounded::min accepts an arbitrary number of arguments and returns the least value as determined by operator<. When comparing integer types, if std::numeric_limits reports that the range of one is entirely less than the range of the other (for instance, bounded::integer<0, 5> is definitely less than bounded::integer<6, 10>), then the comparison function is not invoked. For bounded::integer arguments, the range of the result is the minimum of all the minimums and the minimum of all the maximums.

bounded::max is the same as bounded::min, except that it uses operator>.

There is a more general function, bounded::extreme. The first argument to this function is a comparison function that returns true if the first value is more extreme than the second. The result of the function is the most extreme value as determined by this comparison function. The type of the result by default uses std::common_type, but that can be changed by providing a specialization of template<typename Compare, typename T1, typename T2> class extreme_type in the bounded namespace that provides a member type named type. T1 and T2 will always be decayed (cv-qualifiers and references stripped).

Math

bounded::abs accepts any bounded::integer and returns a new bounded::integer that is guaranteed to be non-negative. The result type has the same overflow_policy type and storage as the argument. noexcept.

Casting functions

increase_min

increase_min accepts an integer as a template argument and a bounded::integer as a function argument. The return type is a bounded::integer that has a new minimum that is the greater of its original minimum and the specified minimum. The function also accepts more arguments, which will be passed to the constructor of the new bounded::integer type.

decrease_max

decrease_max is the same as the above, except the template argument can lower the maximum rather than raise the minimum.

bounded::make

template<
	typename T,
	typename overflow_policy = see below,
	storage_type storage = storage_type::fast
>
using equivalent_type = integer<
	detail::basic_numeric_limits<T>::min(),
	detail::basic_numeric_limits<T>::max(),
	overflow_policy,
	storage
>;

This alias template allows the user to specify a type and get its equivalent bounded version. For instance, bounded::equivalent_type<std::int8_t> is the same as bounded::integer<-128, 127, bounded::null_policy> on most systems. The defaulted overflow_policy parameter is bounded::null_policy for signed built-in integer types and the same as T::overflow_policy_type for bounded::integer types. Currently unsigned built-in types default to bounded::null_policy, but this is a temporary defect caused by not having a bounded::modulo_policy.

template<typename overflow_policy = see below, storage_type storage = storage_type::fast, typename T = unspecified>
constexpr auto make(T const value) noexcept -> equivalent_type<T, overflow_policy, storage>;

It is intended the the user specifies 0, 1, or 2 template arguments. A call like make(value) will return an instance of the type specified by bounded::equivalent_type<decltype(value)>. However, the user can explicitly specify the overflow_policy and the storage to be used by passing them as the first and second template arguments, respectively.

template<intmax_t value, typename overflow_policy = null_policy, storage_type storage = storage_type::fast>
constexpr auto make() noexcept -> integer<value, value, overflow_policy, storage> {
	return {value, non_check};
}

This overload is used to create a bounded::integer that has a known compile-time value. The purpose is to reduce the duplication of the value as in a call to bounded::integer<5, 5>(5) . Instead, you would simply use bounded::make<5>() .

bounded::integer literals

The namespace bounded::literal contains a user defined integer literal _bi. This creates a bounded::integer with a min and max equal to the value. For example, 10_bi is the same as bounded::integer<10, 10, bounded::null_policy>(10) .

Iterators

bounded::next is the same as std::next, except the default value is 1_bi instead of 1 (type of bounded::integer<1, 1> instead of int). bounded::prev is the same as std::prev, except its default value is also 1_bi instead of 1.

is_bounded_integer

For a given type T, bounded::is_bounded_integer derives from std::true_type if the type is a bounded::integer, otherwise it derives from std::false_type.

Conditional operator

`BOUNDED_CONDITIONAL` is a macro that accepts three arguments. The first argument is evaluated in `operator?:` as the first (boolean) argument. The result of the macro is the second argument if the condition is true, otherwise it is the third argument. The type of the result is the common type (as determined by std::common_type) and value category of the second and third arguments.

This exists as a workaround for a limitation of `operator?:`. The built-in operator attempts to convert each argument to the type of the other. This means that a statement like `true ? 1_bi : 2_bi` does not compile, as neither argument can be converted to the other. `BOUNDED_CONDITIONAL(true, 1_bi, 2_bi)`, however, returns the value 1, and the type is bounded::integer<1, 2>.

std::common_type

This library provides specializations for the class template std::common_type. The common_type of two bounded::integer types is another bounded::integer type. The minimum is the lesser of the two minimums, the maximum is the greater of the two maximums, and the policy is the result of common_policy. This specialization only exists when both arguments have the same storage_type.

count / count_if

The bounded library provides functions that are nearly identical to std::count and std::count_if. The difference is the type returned. bounded::count and bounded::count_if return the type bounded::integer<0, std::numeric_limits<iterator_difference_type>::max()>.

bounded::array

integer_range

The header bounded_integer/integer_range.hpp defines two function overloads of bounded::integer_range: bounded::integer_range(begin, end) and bounded::integer_range(size), where begin, end, and size are all integers. bounded::integer_range(size) is equivalent to bounded::integer_range(0, size). The return type of this function is such that it can be used in a range-based for loop.

The given range contains all the values from begin to end, including begin but excluding end. For example, the code for (auto x : bounded::integer_range(5_bi) will result in x having the values 0, 1, 2, 3, and 4. The type of x is bounded::integer<0, 4>. The numbers are generated on demand, so it has O(1) space complexity in the size of the range.