Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions doc/modules/ROOT/pages/signed_integers.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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<BasisType>::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<i32>( // OK: convert, mask, convert back
static_cast<u32>(s) & u32{0xFF}
);
----

== Mixed-Width Operations

Operations between different width safe signed integer types are compile-time errors.
Expand Down
77 changes: 77 additions & 0 deletions include/boost/safe_numbers/detail/signed_integer_basis.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,36 @@ class signed_integer_basis
template <fundamental_signed_integral OtherBasis>
BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator%=(signed_integer_basis<OtherBasis> rhs) -> signed_integer_basis&;

BOOST_SAFE_NUMBERS_HOST_DEVICE constexpr auto operator&=(signed_integer_basis) -> signed_integer_basis&
{
static_assert(dependent_false<BasisType>, "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<BasisType>, "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<BasisType>, "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<BasisType>, "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<BasisType>, "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;
Expand Down Expand Up @@ -3014,6 +3044,53 @@ template <overflow_policy Policy, detail::fundamental_signed_integral BasisType>
}
}

template <detail::fundamental_signed_integral BasisType>
constexpr auto operator~(const detail::signed_integer_basis<BasisType> lhs) noexcept
{
static_assert(detail::dependent_false<BasisType>, "Bitwise NOT is deliberately disabled for signed safe integers (see Ada)");
return lhs; // LCOV_EXCL_LINE : deliberately unreachable
}

template <detail::fundamental_signed_integral BasisType>
constexpr auto operator&(const detail::signed_integer_basis<BasisType> lhs,
const detail::signed_integer_basis<BasisType>) noexcept
{
static_assert(detail::dependent_false<BasisType>, "Bitwise AND is deliberately disabled for signed safe integers (see Ada)");
return lhs; // LCOV_EXCL_LINE : deliberately unreachable
}

template <detail::fundamental_signed_integral BasisType>
constexpr auto operator|(const detail::signed_integer_basis<BasisType> lhs,
const detail::signed_integer_basis<BasisType>) noexcept
{
static_assert(detail::dependent_false<BasisType>, "Bitwise OR is deliberately disabled for signed safe integers (see Ada)");
return lhs; // LCOV_EXCL_LINE : deliberately unreachable
}

template <detail::fundamental_signed_integral BasisType>
constexpr auto operator^(const detail::signed_integer_basis<BasisType> lhs,
const detail::signed_integer_basis<BasisType>) noexcept
{
static_assert(detail::dependent_false<BasisType>, "Bitwise XOR is deliberately disabled for signed safe integers (see Ada)");
return lhs; // LCOV_EXCL_LINE : deliberately unreachable
}

template <detail::fundamental_signed_integral BasisType>
constexpr auto operator<<(const detail::signed_integer_basis<BasisType> lhs,
const detail::signed_integer_basis<BasisType>) noexcept
{
static_assert(detail::dependent_false<BasisType>, "Left shift is deliberately disabled for signed safe integers (see Ada)");
return lhs; // LCOV_EXCL_LINE : deliberately unreachable
}

template <detail::fundamental_signed_integral BasisType>
constexpr auto operator>>(const detail::signed_integer_basis<BasisType> lhs,
const detail::signed_integer_basis<BasisType>) noexcept
{
static_assert(detail::dependent_false<BasisType>, "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
Expand Down
1 change: 1 addition & 0 deletions test/Jamfile
Original file line number Diff line number Diff line change
Expand Up @@ -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 ;
Expand Down
31 changes: 31 additions & 0 deletions test/compile_fail_signed_bitwise.cpp
Original file line number Diff line number Diff line change
@@ -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 <boost/safe_numbers/signed_integers.hpp>

#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<void>(result);

return 0;
}
Loading