diff --git a/doc/modules/ROOT/pages/signed_integers.adoc b/doc/modules/ROOT/pages/signed_integers.adoc index cdf4b96..d0523b3 100644 --- a/doc/modules/ROOT/pages/signed_integers.adoc +++ b/doc/modules/ROOT/pages/signed_integers.adoc @@ -135,6 +135,26 @@ constexpr auto operator-() const -> signed_integer_basis; - `+`: Returns a copy of the value (identity). - `-`: Throws `std::domain_error` if the value is `std::numeric_limits::min()`, since negating the minimum value of a two's complement signed integer overflows. +== Bitwise Operations + +Signed integer types in this library deliberately do **not** provide bitwise operations. +Any attempt to use `~`, `&`, `|`, `^`, `<<`, or `>>` — or the corresponding compound-assignment forms `&=`, `|=`, `^=`, `<<=`, `>>=` on a signed safe integer is a compile-time error. + +This design follows the precedent set by the https://ada-lang.io/[Ada] programming language, which treats signed integers as purely numeric values and reserves bit-level manipulation for distinct modular (unsigned) types. +The rationale is that bitwise reasoning about a two's-complement sign bit is a frequent source of portability and correctness bugs: right-shifting a negative value, masking off the high bit, or reinterpreting the result of an arithmetic operation as a bit pattern all invite surprise and, historically in C and C++, implementation-defined or undefined behavior. + +Code that genuinely needs bit manipulation should convert the value to the corresponding unsigned safe integer type, perform the operation, and convert back. + +[source,c++] +---- +auto s = i32{0x1234}; + +// auto m = s & i32{0xFF}; // Compile error: bitwise ops disabled for signed +auto m = static_cast( // OK: convert, mask, convert back + static_cast(s) & u32{0xFF} +); +---- + == Mixed-Width Operations Operations between different width safe signed integer types are compile-time errors. diff --git a/include/boost/safe_numbers/detail/signed_integer_basis.hpp b/include/boost/safe_numbers/detail/signed_integer_basis.hpp index 9b3b3e2..df3c98f 100644 --- a/include/boost/safe_numbers/detail/signed_integer_basis.hpp +++ b/include/boost/safe_numbers/detail/signed_integer_basis.hpp @@ -79,6 +79,36 @@ class signed_integer_basis template BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator%=(signed_integer_basis rhs) -> signed_integer_basis&; + BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator&=(signed_integer_basis) -> signed_integer_basis& + { + static_assert(dependent_false, "Bitwise AND is deliberately disabled for signed safe integers (see Ada)"); + return *this; // LCOV_EXCL_LINE : deliberately unreachable + } + + BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator|=(signed_integer_basis) -> signed_integer_basis& + { + static_assert(dependent_false, "Bitwise OR is deliberately disabled for signed safe integers (see Ada)"); + return *this; // LCOV_EXCL_LINE : deliberately unreachable + } + + BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator^=(signed_integer_basis) -> signed_integer_basis& + { + static_assert(dependent_false, "Bitwise XOR is deliberately disabled for signed safe integers (see Ada)"); + return *this; // LCOV_EXCL_LINE : deliberately unreachable + } + + BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator<<=(signed_integer_basis) -> signed_integer_basis& + { + static_assert(dependent_false, "Left shift is deliberately disabled for signed safe integers (see Ada)"); + return *this; // LCOV_EXCL_LINE : deliberately unreachable + } + + BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator>>=(signed_integer_basis) -> signed_integer_basis& + { + static_assert(dependent_false, "Right shift is deliberately disabled for signed safe integers (see Ada)"); + return *this; // LCOV_EXCL_LINE : deliberately unreachable + } + BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator++() -> signed_integer_basis&; BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator++(int) -> signed_integer_basis; @@ -3014,6 +3044,53 @@ template } } +template +constexpr auto operator~(const detail::signed_integer_basis lhs) noexcept +{ + static_assert(detail::dependent_false, "Bitwise NOT is deliberately disabled for signed safe integers (see Ada)"); + return lhs; // LCOV_EXCL_LINE : deliberately unreachable +} + +template +constexpr auto operator&(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis) noexcept +{ + static_assert(detail::dependent_false, "Bitwise AND is deliberately disabled for signed safe integers (see Ada)"); + return lhs; // LCOV_EXCL_LINE : deliberately unreachable +} + +template +constexpr auto operator|(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis) noexcept +{ + static_assert(detail::dependent_false, "Bitwise OR is deliberately disabled for signed safe integers (see Ada)"); + return lhs; // LCOV_EXCL_LINE : deliberately unreachable +} + +template +constexpr auto operator^(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis) noexcept +{ + static_assert(detail::dependent_false, "Bitwise XOR is deliberately disabled for signed safe integers (see Ada)"); + return lhs; // LCOV_EXCL_LINE : deliberately unreachable +} + +template +constexpr auto operator<<(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis) noexcept +{ + static_assert(detail::dependent_false, "Left shift is deliberately disabled for signed safe integers (see Ada)"); + return lhs; // LCOV_EXCL_LINE : deliberately unreachable +} + +template +constexpr auto operator>>(const detail::signed_integer_basis lhs, + const detail::signed_integer_basis) noexcept +{ + static_assert(detail::dependent_false, "Right shift is deliberately disabled for signed safe integers (see Ada)"); + return lhs; // LCOV_EXCL_LINE : deliberately unreachable +} + } // namespace boost::safe_numbers #undef BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP diff --git a/test/Jamfile b/test/Jamfile index 11871fd..70f32e7 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -62,6 +62,7 @@ run test_signed_multiplication.cpp ; run test_signed_division.cpp ; run test_signed_modulo.cpp ; run test_signed_inc_dec.cpp ; +compile-fail compile_fail_signed_bitwise.cpp ; run test_unsigned_addition.cpp ; compile-fail compile_fail_unsigned_addition.cpp ; diff --git a/test/compile_fail_signed_bitwise.cpp b/test/compile_fail_signed_bitwise.cpp new file mode 100644 index 0000000..14e1bb3 --- /dev/null +++ b/test/compile_fail_signed_bitwise.cpp @@ -0,0 +1,31 @@ +// Copyright 2026 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +// Signed safe integers deliberately do not provide bitwise operations +// (see the Ada programming language for the rationale). This test +// verifies that any attempted use of a bitwise operator on a signed +// safe integer is a compile-time error. + +#ifdef BOOST_SAFE_NUMBERS_BUILD_MODULE + +import boost.safe_numbers; + +#else + +#include + +#endif + +int main() +{ + using namespace boost::safe_numbers; + + const auto a {i32{0x1234}}; + const auto b {i32{0x00FF}}; + + const auto result {a & b}; // Should fail to compile + static_cast(result); + + return 0; +}