User-Defined Literals
Description
The library provides user-defined literal suffixes for concise construction of safe numeric types.
The literals are defined in the boost::safe_numbers::literals namespace.
The behavior of each literal depends on the target type:
-
For
_u8,_u16, and_u32, the literal value is range-checked and throwsstd::overflow_errorif the value exceeds the target type’s maximum. -
The
_u64literal performs no range check sinceunsigned long longmaps directly tostd::uint64_t(statically verified). -
The
_u128literal accepts an integer-form token, parses the digit string, and throwsstd::overflow_erroron overflow orstd::invalid_argumenton invalid input. -
For
_i8,_i16,_i32, and_i64, the literal magnitude is range-checked against the signed maximum and throwsstd::overflow_errorif it exceeds it. Negative values are formed by applying the wrapper’s unaryoperator-to the positive literal: the expression-42_i32parses as-(42_i32). -
The
_i128literal mirrors_u128: it parses an integer-form digit string intoint128_tand throws on overflow or invalid input. -
For
_f32and_f64, the literal is taken aslong doubleand range-checked against the target’s maximum, throwingstd::overflow_erroron overflow.
#include <boost/safe_numbers/literals.hpp>
namespace boost::safe_numbers::literals {
constexpr auto operator ""_u8(unsigned long long int val) -> u8;
constexpr auto operator ""_u16(unsigned long long int val) -> u16;
constexpr auto operator ""_u32(unsigned long long int val) -> u32;
constexpr auto operator ""_u64(unsigned long long int val) noexcept -> u64;
constexpr auto operator ""_u128(const char* str) -> u128;
constexpr auto operator ""_i8(unsigned long long int val) -> i8;
constexpr auto operator ""_i16(unsigned long long int val) -> i16;
constexpr auto operator ""_i32(unsigned long long int val) -> i32;
constexpr auto operator ""_i64(unsigned long long int val) -> i64;
constexpr auto operator ""_i128(const char* str) -> i128;
constexpr auto operator ""_f32(long double val) -> f32;
constexpr auto operator ""_f64(long double val) -> f64;
} // namespace boost::safe_numbers::literals
Literal Suffixes
| Suffix | Result Type | Range Check |
|---|---|---|
|
|
Throws |
|
|
Throws |
|
|
Throws |
|
|
No range check (direct conversion from |
|
|
Parses digit string; throws |
|
|
Throws |
|
|
Throws |
|
|
Throws |
|
|
Throws |
|
|
Parses digit string; throws |
|
|
Throws |
|
|
Throws |
Usage
To use the literals, bring the boost::safe_numbers::literals namespace into scope:
using namespace boost::safe_numbers::literals;
constexpr auto a {42_u8};
constexpr auto b {1000_u16};
constexpr auto c {100000_u32};
constexpr auto d {9999999999_u64};
constexpr auto e {340282366920938463463374607431768211455_u128};
constexpr auto f {-42_i8};
constexpr auto g {32767_i16};
constexpr auto h {-2147483647_i32};
constexpr auto i {9223372036854775807_i64};
constexpr auto j {-170141183460469231731687303715884105727_i128};
constexpr auto k {3.14_f32};
constexpr auto l {2.718281828459045_f64};
Literals are constexpr and can be used in compile-time contexts.
When used in a constexpr context, an out-of-range value produces a compile error rather than a runtime exception.
Negative Signed Literals
C++ user-defined literal operators never see a leading minus sign as part of their input.
The expression -42_i32 is parsed as -(42_i32): the literal returns a positive i32 and unary operator- on the wrapper produces the negative result.
Because the literal’s range check is against the positive maximum (INT_MAX), the asymmetric INT_MIN value is not directly expressible through the literal: writing -128_i8 parses as -(128_i8), and the inner literal throws because 128 > INT8_MAX.
Use direct construction for that one value:
constexpr auto min_i8 {i8{std::numeric_limits<std::int8_t>::min()}};
The same asymmetry applies to _i16, _i32, _i64, and _i128.
Examples
// Copyright 2025 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
// This example demonstrates the use of user-defined literals for
// constructing safe numeric types. The literals provide a concise
// syntax and perform compile-time range checking when possible.
#include <boost/safe_numbers/unsigned_integers.hpp>
#include <boost/safe_numbers/signed_integers.hpp>
#include <boost/safe_numbers/floats.hpp>
#include <boost/safe_numbers/literals.hpp>
#include <boost/safe_numbers/iostream.hpp>
#include <iostream>
int main()
{
using namespace boost::safe_numbers;
using namespace boost::safe_numbers::literals;
// Construct safe unsigned integers using literal suffixes
{
constexpr auto a {42_u8};
constexpr auto b {1000_u16};
constexpr auto c {100000_u32};
constexpr auto d {9999999999_u64};
constexpr auto e {340282366920938463463374607431768211455_u128};
std::cout << "42_u8 = " << a << std::endl;
std::cout << "1000_u16 = " << b << std::endl;
std::cout << "100000_u32 = " << c << std::endl;
std::cout << "9999999999_u64 = " << d << std::endl;
std::cout << "max_u128 = " << e << std::endl;
}
// Signed integer literals. Negative values are formed by applying unary
// minus to a positive literal: -42_i32 parses as -(42_i32).
{
constexpr auto a {-42_i8};
constexpr auto b {32767_i16};
constexpr auto c {-2147483647_i32};
constexpr auto d {9223372036854775807_i64};
constexpr auto e {-170141183460469231731687303715884105727_i128};
std::cout << "-42_i8 = " << a << std::endl;
std::cout << "max_i16 = " << b << std::endl;
std::cout << "min+1_i32 = " << c << std::endl;
std::cout << "max_i64 = " << d << std::endl;
std::cout << "min+1_i128 = " << e << std::endl;
}
// Floating-point literals. The literal accepts a long double and is
// range-checked against the target type's maximum.
{
constexpr auto pi {3.14_f32};
constexpr auto e {2.718281828459045_f64};
std::cout << "pi_f32 = " << pi << std::endl;
std::cout << "e_f64 = " << e << std::endl;
}
// Literals work naturally in expressions
{
const auto sum {100_u32 + 50_u32};
const auto product {6_u32 * 7_u32};
std::cout << "100_u32 + 50_u32 = " << sum << std::endl;
std::cout << "6_u32 * 7_u32 = " << product << std::endl;
}
// Literals are constexpr - can be used in compile-time contexts
{
constexpr auto compile_time {255_u8};
static_assert(compile_time == u8{255U});
constexpr auto zero {0_u32};
static_assert(zero == u32{0U});
std::cout << "constexpr 255_u8 = " << compile_time << std::endl;
}
// NOTE: Out-of-range literals throw std::overflow_error at runtime,
// or produce a compile error when used in constexpr context:
//
// auto bad = 256_u8; // throws std::overflow_error (> UINT8_MAX)
// auto bad = 70000_u16; // throws std::overflow_error (> UINT16_MAX)
// auto bad = 128_i8; // throws std::overflow_error (> INT8_MAX)
// auto bad = 1.0e40_f32; // throws std::overflow_error (> FLT_MAX)
return 0;
}
Output:
42_u8 = 42 1000_u16 = 1000 100000_u32 = 100000 9999999999_u64 = 9999999999 max_u128 = 340282366920938463463374607431768211455 -42_i8 = -42 max_i16 = 32767 min+1_i32 = -2147483647 max_i64 = 9223372036854775807 min+1_i128 = -170141183460469231731687303715884105727 pi_f32 = 3.14 e_f64 = 2.71828 100_u32 + 50_u32 = 150 6_u32 * 7_u32 = 42 constexpr 255_u8 = 255