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 throws std::overflow_error if the value exceeds the target type’s maximum.

  • The _u64 literal performs no range check since unsigned long long maps directly to std::uint64_t (statically verified).

  • The _u128 literal accepts an integer-form token, parses the digit string, and throws std::overflow_error on overflow or std::invalid_argument on invalid input.

  • For _i8, _i16, _i32, and _i64, the literal magnitude is range-checked against the signed maximum and throws std::overflow_error if it exceeds it. Negative values are formed by applying the wrapper’s unary operator- to the positive literal: the expression -42_i32 parses as -(42_i32).

  • The _i128 literal mirrors _u128: it parses an integer-form digit string into int128_t and throws on overflow or invalid input.

  • For _f32 and _f64, the literal is taken as long double and range-checked against the target’s maximum, throwing std::overflow_error on 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

_u8

u8

Throws std::overflow_error if value > 255

_u16

u16

Throws std::overflow_error if value > 65,535

_u32

u32

Throws std::overflow_error if value > 4,294,967,295

_u64

u64

No range check (direct conversion from unsigned long long)

_u128

u128

Parses digit string; throws std::overflow_error on overflow, std::invalid_argument on invalid input

_i8

i8

Throws std::overflow_error if magnitude > 127

_i16

i16

Throws std::overflow_error if magnitude > 32,767

_i32

i32

Throws std::overflow_error if magnitude > 2,147,483,647

_i64

i64

Throws std::overflow_error if magnitude > 9,223,372,036,854,775,807

_i128

i128

Parses digit string; throws std::overflow_error on overflow, std::invalid_argument on invalid input

_f32

f32

Throws std::overflow_error if value > std::numeric_limits<float>::max()

_f64

f64

Throws std::overflow_error if value > std::numeric_limits<double>::max()

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

Example 1. This example demonstrates how to use user-defined literals with safe numeric types.
// 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