diff --git a/doc/modules/ROOT/nav.adoc b/doc/modules/ROOT/nav.adoc index 7150ddf..206f032 100644 --- a/doc/modules/ROOT/nav.adoc +++ b/doc/modules/ROOT/nav.adoc @@ -8,14 +8,18 @@ ** xref:examples.adoc#examples_wrapping[Wrapping Arithmetic] ** xref:examples.adoc#examples_strict[Strict Arithmetic] ** xref:examples.adoc#examples_generic[Generic Policy-Parameterized Arithmetic] +** xref:examples.adoc#examples_literals[Literals] ** xref:examples.adoc#examples_charconv[Character Conversion] ** xref:examples.adoc#examples_fmt_format[Formatting] +** xref:examples.adoc#examples_iostream[Stream I/O] * xref:api_reference.adoc[] ** xref:api_reference.adoc#api_types[Types] ** xref:api_reference.adoc#api_functions[Functions] ** xref:api_reference.adoc#api_headers[Headers] * xref:unsigned_integers.adoc[] * xref:policies.adoc[] +* xref:literals.adoc[] * xref:limits.adoc[] * xref:format.adoc[] +* xref:iostream.adoc[] * xref:charconv.adoc[] diff --git a/doc/modules/ROOT/pages/api_reference.adoc b/doc/modules/ROOT/pages/api_reference.adoc index 1eb90f3..0c3aed8 100644 --- a/doc/modules/ROOT/pages/api_reference.adoc +++ b/doc/modules/ROOT/pages/api_reference.adoc @@ -59,6 +59,26 @@ https://www.boost.org/LICENSE_1_0.txt | Parses a character string into a safe integer |=== +=== Literals + +[cols="1,2", options="header"] +|=== +| Literal | Description + +| xref:literals.adoc[`_u8`, `_u16`, `_u32`, `_u64`, `_u128`] +| User-defined literal suffixes for constructing safe integer types +|=== + +=== Stream I/O + +[cols="1,2", options="header"] +|=== +| Function | Description + +| xref:iostream.adoc[`operator<<`, `operator>>`] +| Stream insertion and extraction for all safe integer types +|=== + === Arithmetic [cols="1,2", options="header"] @@ -97,6 +117,12 @@ https://www.boost.org/LICENSE_1_0.txt | `` | The `overflow_policy` enum class +| `` +| User-defined literal suffixes (`_u8`, `_u16`, `_u32`, `_u64`, `_u128`) + +| `` +| Stream I/O operators (`operator<<`, `operator>>`) for safe integer types + | `` | Character conversion functions (`to_chars`, `from_chars`) diff --git a/doc/modules/ROOT/pages/examples.adoc b/doc/modules/ROOT/pages/examples.adoc index 381ddf2..5147b03 100644 --- a/doc/modules/ROOT/pages/examples.adoc +++ b/doc/modules/ROOT/pages/examples.adoc @@ -203,6 +203,32 @@ add(max, 1) = 0 ---- ==== +[#examples_literals] +== Literals + +User-defined literal suffixes (`_u8`, `_u16`, `_u32`, `_u64`, `_u128`) provide concise construction of safe integer types. +Values are range-checked at construction, producing a compile error in `constexpr` contexts or throwing `std::overflow_error` at runtime for out-of-range values. + +.This https://github.com/boostorg/safe_numbers/blob/develop/examples/literals.cpp[example] demonstrates how to use user-defined literals with safe integer types. +==== +[source, c++] +---- +include::example$literals.cpp[] +---- + +Output: +---- +42_u8 = 42 +1000_u16 = 1000 +100000_u32 = 100000 +9999999999_u64 = 9999999999 +max_u128 = 340282366920938463463374607431768211455 +100_u32 + 50_u32 = 150 +6_u32 * 7_u32 = 42 +constexpr 255_u8 = 255 +---- +==== + [#examples_charconv] == Character Conversion @@ -272,6 +298,35 @@ Fill Character: ---- ==== +[#examples_iostream] +== Stream I/O + +The library provides `operator<<` and `operator>>` overloads for all safe integer types. +The `u8` type is handled specially: it displays as a numeric value rather than as a character. + +.This https://github.com/boostorg/safe_numbers/blob/develop/examples/iostream.cpp[example] demonstrates stream I/O with safe integer types. +==== +[source, c++] +---- +include::example$iostream.cpp[] +---- + +Output: +---- +u8: 42 +u16: 1000 +u32: 100000 +u64: 9999999999 +u8(10) = 10 (not a newline character) +u8(32) = 32 (not a space character) +Read from stream: 12345 +Decimal: 255 +Hexadecimal: ff +Octal: 377 +Roundtrip: 18446744073709551615 -> "18446744073709551615" -> 18446744073709551615 +---- +==== + [#examples_policy_comparison] == Policy Comparison diff --git a/doc/modules/ROOT/pages/iostream.adoc b/doc/modules/ROOT/pages/iostream.adoc new file mode 100644 index 0000000..0a566a8 --- /dev/null +++ b/doc/modules/ROOT/pages/iostream.adoc @@ -0,0 +1,98 @@ +//// +Copyright 2026 Matt Borland +Distributed under the Boost Software License, Version 1.0. +https://www.boost.org/LICENSE_1_0.txt +//// + +[#iostream] + += Stream I/O Support + +== Description + +The library provides `operator<<` and `operator>>` overloads for all safe integer types, enabling use with standard C++ streams (`std::cout`, `std::cin`, `std::stringstream`, etc.). + +The `u8` type is handled specially: it displays as a numeric value rather than as a character, unlike `std::uint8_t` which most implementations treat as `unsigned char`. + +Attempting to read a negative value into an unsigned safe integer throws `std::domain_error`. + +[source,c++] +---- +#include + +namespace boost::safe_numbers::detail { + +// Output: writes the numeric value to the stream +template +auto operator<<(std::basic_ostream& os, const LibType& v) + -> std::basic_ostream&; + +// Input: reads a numeric value from the stream +// Throws std::domain_error if the input begins with '-' +template +auto operator>>(std::basic_istream& is, LibType& v) + -> std::basic_istream&; + +} // namespace boost::safe_numbers::detail +---- + +== Behavior + +=== Output (`operator<<`) + +|=== +| Type | Behavior + +| `u8` +| Promoted to `std::uint32_t` before output so the value displays as a number, not a character + +| `u16`, `u32`, `u64` +| Converted to the underlying type and written directly + +| `u128` +| Converted to the underlying `uint128_t` and written using its stream support +|=== + +Standard stream manipulators (`std::hex`, `std::oct`, `std::dec`, `std::setw`, etc.) work as expected. + +=== Input (`operator>>`) + +|=== +| Input | Behavior + +| Valid non-negative number +| Parsed and stored in the safe integer + +| Negative number (starts with `-`) +| Throws `std::domain_error` + +| Invalid input +| Sets the stream's fail bit as with any standard stream extraction +|=== + +For `u8`, the input is read as a `std::uint32_t` to avoid character interpretation, then narrowed to `std::uint8_t`. + +== Examples + +.This https://github.com/boostorg/safe_numbers/blob/develop/examples/iostream.cpp[example] demonstrates stream I/O with safe integer types. +==== +[source, c++] +---- +include::example$iostream.cpp[] +---- + +Output: +---- +u8: 42 +u16: 1000 +u32: 100000 +u64: 9999999999 +u8(10) = 10 (not a newline character) +u8(32) = 32 (not a space character) +Read from stream: 12345 +Decimal: 255 +Hexadecimal: ff +Octal: 377 +Roundtrip: 18446744073709551615 -> "18446744073709551615" -> 18446744073709551615 +---- +==== diff --git a/doc/modules/ROOT/pages/literals.adoc b/doc/modules/ROOT/pages/literals.adoc new file mode 100644 index 0000000..fba7930 --- /dev/null +++ b/doc/modules/ROOT/pages/literals.adoc @@ -0,0 +1,98 @@ +//// +Copyright 2026 Matt Borland +Distributed under the Boost Software License, Version 1.0. +https://www.boost.org/LICENSE_1_0.txt +//// + +[#literals] + += User-Defined Literals + +== Description + +The library provides user-defined literal suffixes for concise construction of safe integer types. +The literals are defined in the `boost::safe_numbers::literals` namespace. +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`. +The `_u128` literal parses a string representation and throws `std::overflow_error` on overflow or `std::invalid_argument` on invalid input. + +[source,c++] +---- +#include + +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) -> u64; +constexpr auto operator ""_u128(const char* str) -> u128; + +} // 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 string; throws `std::overflow_error` on overflow, `std::invalid_argument` on invalid input +|=== + +== Usage + +To use the literals, bring the `boost::safe_numbers::literals` namespace into scope: + +[source,c++] +---- +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}; +---- + +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. + +== Examples + +.This https://github.com/boostorg/safe_numbers/blob/develop/examples/literals.cpp[example] demonstrates how to use user-defined literals with safe integer types. +==== +[source, c++] +---- +include::example$literals.cpp[] +---- + +Output: +---- +42_u8 = 42 +1000_u16 = 1000 +100000_u32 = 100000 +9999999999_u64 = 9999999999 +max_u128 = 340282366920938463463374607431768211455 +100_u32 + 50_u32 = 150 +6_u32 * 7_u32 = 42 +constexpr 255_u8 = 255 +---- +==== diff --git a/examples/iostream.cpp b/examples/iostream.cpp new file mode 100644 index 0000000..0d5a956 --- /dev/null +++ b/examples/iostream.cpp @@ -0,0 +1,76 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +//[iostream_example +//` This example demonstrates stream I/O support for safe integer types. +//` Safe integers can be written to and read from standard streams using +//` the familiar << and >> operators. The u8 type displays as a number +//` rather than as a character. + +#include +#include +#include +#include +#include +#include + +int main() +{ + using namespace boost::safe_numbers; + + // Output: safe integers work with std::cout + { + const u8 a {42U}; + const u16 b {1000U}; + const u32 c {100000U}; + const u64 d {9999999999ULL}; + + std::cout << "u8: " << a << std::endl; + std::cout << "u16: " << b << std::endl; + std::cout << "u32: " << c << std::endl; + std::cout << "u64: " << d << std::endl; + } + + // u8 displays as a number, not a character + { + const u8 newline {10U}; + const u8 space {32U}; + + std::cout << "u8(10) = " << newline << " (not a newline character)" << std::endl; + std::cout << "u8(32) = " << space << " (not a space character)" << std::endl; + } + + // Input: safe integers work with std::istringstream + { + u32 value; + std::istringstream input {"12345"}; + input >> value; + + std::cout << "Read from stream: " << value << std::endl; + } + + // Works with stream formatting + { + const u32 val {255U}; + std::cout << "Decimal: " << std::dec << val << std::endl; + std::cout << "Hexadecimal: " << std::hex << val << std::endl; + std::cout << "Octal: " << std::oct << val << std::endl; + std::cout << std::dec; // Reset to decimal + } + + // Roundtrip through a stringstream + { + const u64 original {18446744073709551615ULL}; + std::stringstream ss; + ss << original; + + u64 restored; + ss >> restored; + + std::cout << "Roundtrip: " << original << " -> \"" << original << "\" -> " << restored << std::endl; + } + + return 0; +} +//] diff --git a/examples/literals.cpp b/examples/literals.cpp new file mode 100644 index 0000000..6b62098 --- /dev/null +++ b/examples/literals.cpp @@ -0,0 +1,63 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +//[literals_example +//` This example demonstrates the use of user-defined literals for +//` constructing safe integer types. The literals provide a concise +//` syntax and perform compile-time range checking when possible. + +#include +#include +#include +#include + +int main() +{ + using namespace boost::safe_numbers; + using namespace boost::safe_numbers::literals; + + // Construct safe 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; + } + + // 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) + + return 0; +} +//] diff --git a/test/Jamfile b/test/Jamfile index 5be6ce0..6775830 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -120,5 +120,7 @@ run ../examples/checked_arithmetic.cpp ; run ../examples/wrapping_arithmetic.cpp ; run ../examples/strict_arithmetic.cpp ; run ../examples/generic_arithmetic.cpp ; +run ../examples/literals.cpp ; run ../examples/fmt_format.cpp ; +run ../examples/iostream.cpp ; run ../examples/charconv.cpp ;