From 35f0a84be9d87bcd9a1878c7181ece16001fb3a8 Mon Sep 17 00:00:00 2001 From: Sean Parent Date: Tue, 30 Sep 2025 17:58:37 -0700 Subject: [PATCH 1/2] Improving Docs Improving docs and adding additional variants of + and - for bitmasks --- .vscode/settings.json | 6 + CMakeLists.txt | 4 +- CMakePresets.json | 2 +- include/stlab/enum_ops.hpp | 316 +++++++++++++++++++++++-------------- tests/enum_ops_tests.cpp | 26 +-- 5 files changed, 222 insertions(+), 132 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..d6e5e4e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "cSpell.words": [ + "dontinclude", + "NOLINTNEXTLINE" + ] +} diff --git a/CMakeLists.txt b/CMakeLists.txt index 24deec0..c7ab98c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,12 +8,12 @@ include(cmake/CPM.cmake) # Fetch cpp-library via CPM +# https://github.com/stlab/cpp-library/releases # CPMAddPackage( # NAME cpp-library # URL "${CMAKE_SOURCE_DIR}/../cpp-library" # ) -# https://github.com/stlab/cpp-library/releases -CPMAddPackage("gh:stlab/cpp-library@4.0.1") +CPMAddPackage("gh:stlab/cpp-library@4.0.3") include(${cpp-library_SOURCE_DIR}/cpp-library.cmake) # Let cpp-library handle the project declaration and version detection diff --git a/CMakePresets.json b/CMakePresets.json index cdf89f1..1e805dd 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -73,7 +73,7 @@ "buildPresets": [ { "name": "default", "displayName": "Default Build", "configurePreset": "default" }, { "name": "test", "displayName": "Build Tests", "configurePreset": "test" }, - { "name": "docs", "displayName": "Build Docs", "configurePreset": "docs", "targets": ["docs"] }, + { "name": "docs", "displayName": "Build Docs", "configurePreset": "docs", "targets": "docs" }, { "name": "clang-tidy", "displayName": "Build with Clang-Tidy", "configurePreset": "clang-tidy" }, { "name": "init", "displayName": "Initialize Templates", "configurePreset": "init" } ], diff --git a/include/stlab/enum_ops.hpp b/include/stlab/enum_ops.hpp index 7ac551d..2a44cbc 100644 --- a/include/stlab/enum_ops.hpp +++ b/include/stlab/enum_ops.hpp @@ -10,6 +10,7 @@ /**************************************************************************************************/ +#include #include /**************************************************************************************************/ @@ -20,7 +21,50 @@ */ /*! - \mainpage Typesafe Integers and Bit Fields (enums) + \defgroup CustomizationPoints Customization Points + \brief Functions to enable typesafe operations for enum types + + These functions must be overloaded in your enum's namespace to enable the corresponding + operations for your enum type. +*/ + +/*! + \defgroup Traits Traits + \brief Traits for enum types + + These traits are used to determine if an enum type has enabled bitmask or arithmetic operations. +*/ + +/*! + \defgroup BitmaskOperations Bitmask Operations + \brief Bitwise operations for bitmask-enabled enums + + These operations are available when you enable bitmask operations by defining: + ```cpp + auto stlab_enable_bitmask_enum(YourEnum) -> std::true_type; + ``` +*/ + +/*! + \defgroup ArithmeticOperations Arithmetic Operations + \brief Arithmetic operations for arithmetic-enabled enums + + These operations are available when you enable arithmetic operations by defining: + ```cpp + auto stlab_enable_arithmetic_enum(YourEnum) -> std::true_type; + ``` +*/ + +/*! + \defgroup CommonOperations Common Operations + \brief Operations available for both bitmask and arithmetic-enabled enums + + These operations are available for enums that have either bitmask or arithmetic operations + enabled. +*/ + +/*! + \mainpage [![View on GitHub](https://img.shields.io/badge/GitHub-enum--ops-181717?logo=github&style=flat)](https://github.com/stlab/enum-ops) @@ -33,17 +77,23 @@ operations have been defined for an enumeration type, \c E, the result will be of type \c E exactly when the operand(s) are of type \c E. - ```cpp - auto stlab_enable_bitmask_enum(E) -> std::true_type; - ``` - Enables the bitset operations, `~`, `|`, `&`, `^`, `|=`, `&=`, and `^=`, - for enumeration type `E`. + \section Operations Available Operations - ```cpp - auto stlab_enable_arithmetic_enum(E) -> std::true_type; - ``` - Enables the typesafe arithmetic operations `+`, `-`, `*`, `/`, `%`, `+=`, `*=`, `-=`, `/=`, and - `%=`, for enumeration type `E`. + \subsection CustomizationPointsSection Customization Points + \copydoc CustomizationPoints + - \ref CustomizationPoints "View all customization functions" + + \subsection BitmaskOperationsSection Bitmask Operations + \copydoc BitmaskOperations + - \ref BitmaskOperations "View all bitmask operations" + + \subsection ArithmeticOperationsSection Arithmetic Operations + \copydoc ArithmeticOperations + - \ref ArithmeticOperations "View all arithmetic operations" + + \subsection CommonOperationsSection Common Operations + \copydoc CommonOperations + - \ref CommonOperations "View all common operations" \section Definition Definition @@ -73,20 +123,28 @@ namespace stlab { /**************************************************************************************************/ -/// Overload this for your enum in the enum namespace to return std::true_type and enable bitwise -/// operators. +/// \addtogroup CustomizationPoints +/// \{ + +/// Overload this for your enum in the enum namespace to return std::true_type and enable +/// bitwise operators. auto stlab_enable_bitmask_enum(...) -> std::false_type; -/// Overload this for your enum in the enum namespace to return std::true_type and enable arithmetic -/// operators. + +/// Overload this for your enum in the enum namespace to return std::true_type and enable +/// arithmetic operators. auto stlab_enable_arithmetic_enum(...) -> std::false_type; // Don't use the `\ deprecated` Doxygen tag here because clang will warn that the // documentation marks the operations deprecated but the deprecated attribute is missing. + /// \note Use is **deprecated**. Use stlab_enable_bitmask_enum instead. auto adobe_enable_bitmask_enum(...) -> std::false_type; + /// \note Use is **deprecated**. Use stlab_enable_arithmetic_enum instead. auto adobe_enable_arithmetic_enum(...) -> std::false_type; +/// \} + /**************************************************************************************************/ /// The implementation namespace. @@ -110,16 +168,6 @@ using has_enabled_arithmetic_t = decltype(stlab_enable_arithmetic_enum(std::decl template using has_deprecated_arithmetic_t = decltype(adobe_enable_arithmetic_enum(std::declval())); -template -constexpr bool has_enabled_arithmetic = - has_enabled_arithmetic_t::value || has_deprecated_arithmetic_t::value; - -template -using enable_if_bitmask_or_arithmetic = - std::enable_if_t, - stlab::implementation::has_enabled_arithmetic_t>, - U>; - template struct safe_underlying_type; @@ -146,14 +194,37 @@ using is_convertible_to_underlying = /**************************************************************************************************/ +/// \addtogroup Traits +/// \{ + +/// Whether the enum type has enabled bitmask operations. +template +constexpr bool has_enabled_bitmask = implementation::has_enabled_bitmask_t::value || + implementation::has_deprecated_bitmask_t::value; + +/// Whether the enum type has enabled arithmetic operations. +template +constexpr bool has_enabled_arithmetic = implementation::has_enabled_arithmetic_t::value || + implementation::has_deprecated_arithmetic_t::value; + +/// Whether the scalar type `U` is compatible with the enum type `T`. +template +constexpr bool is_compatible_scalar = implementation::is_convertible_to_underlying::value; + +/// \} + +/**************************************************************************************************/ + } // namespace stlab /**************************************************************************************************/ +/// \addtogroup BitmaskOperations +/// \{ + template /// Bitwise AND for bitmask-enabled enums; returns the same enum type. -constexpr auto operator&(T lhs, T rhs) - -> std::enable_if_t, T> { +constexpr auto operator&(T lhs, T rhs) -> std::enable_if_t, T> { using underlying = std::underlying_type_t; // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) return static_cast(static_cast(lhs) & static_cast(rhs)); @@ -161,8 +232,7 @@ constexpr auto operator&(T lhs, T rhs) template /// Bitwise NOT for bitmask-enabled enums; returns the same enum type. -constexpr auto operator~(T a) - -> std::enable_if_t, T> { +constexpr auto operator~(T a) -> std::enable_if_t, T> { using underlying = std::underlying_type_t; // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) return static_cast(~static_cast(a)); @@ -170,8 +240,7 @@ constexpr auto operator~(T a) template /// Bitwise OR for bitmask-enabled enums. -constexpr auto operator|(T lhs, T rhs) - -> std::enable_if_t, T> { +constexpr auto operator|(T lhs, T rhs) -> std::enable_if_t, T> { using underlying = std::underlying_type_t; // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) return static_cast(static_cast(lhs) | static_cast(rhs)); @@ -179,8 +248,7 @@ constexpr auto operator|(T lhs, T rhs) template /// Bitwise XOR for bitmask-enabled enums. -constexpr auto operator^(T lhs, T rhs) - -> std::enable_if_t, T> { +constexpr auto operator^(T lhs, T rhs) -> std::enable_if_t, T> { using underlying = std::underlying_type_t; // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) return static_cast(static_cast(lhs) ^ static_cast(rhs)); @@ -189,7 +257,7 @@ constexpr auto operator^(T lhs, T rhs) template /// Left shift for bitmask-enabled enums. constexpr auto operator<<(T lhs, std::size_t rhs) - -> std::enable_if_t, T> { + -> std::enable_if_t, T> { using underlying = std::make_unsigned_t>; // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) return static_cast(static_cast(lhs) << static_cast(rhs)); @@ -198,7 +266,7 @@ constexpr auto operator<<(T lhs, std::size_t rhs) template /// Right shift for bitmask-enabled enums. constexpr auto operator>>(T lhs, std::size_t rhs) - -> std::enable_if_t, T> { + -> std::enable_if_t, T> { using underlying = std::make_unsigned_t>; // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) return static_cast(static_cast(lhs) >> static_cast(rhs)); @@ -206,74 +274,95 @@ constexpr auto operator>>(T lhs, std::size_t rhs) template /// XOR-assign for bitmask-enabled enums. -constexpr auto operator^=(T& lhs, T rhs) - -> std::enable_if_t, T&> { +constexpr auto operator^=(T& lhs, T rhs) -> std::enable_if_t, T&> { return lhs = lhs ^ rhs; } template /// AND-assign for bitmask-enabled enums. -constexpr auto operator&=(T& lhs, T rhs) - -> std::enable_if_t, T&> { +constexpr auto operator&=(T& lhs, T rhs) -> std::enable_if_t, T&> { return lhs = lhs & rhs; } template /// OR-assign for bitmask-enabled enums. -constexpr auto operator|=(T& lhs, T rhs) - -> std::enable_if_t, T&> { +constexpr auto operator|=(T& lhs, T rhs) -> std::enable_if_t, T&> { return lhs = lhs | rhs; } template /// Left shift-assign for bitmask-enabled enums. constexpr auto operator<<=(T& lhs, std::size_t rhs) - -> std::enable_if_t, T&> { + -> std::enable_if_t, T&> { return lhs = lhs << rhs; } template /// Right shift-assign for bitmask-enabled enums. constexpr auto operator>>=(T& lhs, std::size_t rhs) - -> std::enable_if_t, T&> { + -> std::enable_if_t, T&> { return lhs = lhs >> rhs; } template -/// Subtracts a value convertible to the underlying type from a bitmask-enabled enum. +/// Subtracts a 0 or 1 scalar value from a bitmask-enabled enum. +/// Allows expressions like `e & (e - 1)` to clear the least set bit. constexpr auto operator-(T lhs, U rhs) - -> std::enable_if_t && - stlab::implementation::is_convertible_to_underlying::value, - T> { + -> std::enable_if_t && stlab::is_compatible_scalar, T> { + assert(rhs == 0 || rhs == 1); using underlying = std::underlying_type_t; // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) return static_cast(static_cast(lhs) - static_cast(rhs)); } -/**************************************************************************************************/ - template -/// Unary plus for arithmetic-enabled enums. -constexpr auto operator+(T a) - -> std::enable_if_t, T> { +/// Subtracts a bitmask-enabled enum from `0` +/// `0 - rhs` is equivalent to `-rhs`. +constexpr auto operator-(std::nullptr_t lhs, T rhs) + -> std::enable_if_t, T> { + return -rhs; +} + +template +/// Adds a 0 or 1 scalar value to a bitmask-enabled enum. +/// Allows expressions like `e & (e + 1)` to clear trailing set bits. +constexpr auto operator+(T lhs, U rhs) + -> std::enable_if_t && stlab::is_compatible_scalar, T> { + assert(rhs == 0 || rhs == 1); using underlying = std::underlying_type_t; // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) - return static_cast(+static_cast(a)); + return static_cast(static_cast(lhs) + static_cast(rhs)); } +template +/// Adds a bitmask-enabled enum to `0` or `1`. +/// Allows expressions like `e & (1 + e)` to clear trailing set bits. +constexpr auto operator+(U lhs, T rhs) + -> std::enable_if_t && stlab::is_compatible_scalar, T> { + assert(lhs == 0 || lhs == 1); + using underlying = std::underlying_type_t; + // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) + return static_cast(static_cast(lhs) + static_cast(rhs)); +} + +/// \} + +/**************************************************************************************************/ + +/// \addtogroup ArithmeticOperations +/// \{ + template -/// Unary minus for arithmetic-enabled enums. -constexpr auto operator-(T a) - -> std::enable_if_t, T> { +/// Unary plus for arithmetic-enabled enums. +constexpr auto operator+(T a) -> std::enable_if_t, T> { using underlying = std::underlying_type_t; // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) - return static_cast(-static_cast(a)); + return static_cast(+static_cast(a)); } template /// Addition for arithmetic-enabled enums. -constexpr auto operator+(T lhs, T rhs) - -> std::enable_if_t, T> { +constexpr auto operator+(T lhs, T rhs) -> std::enable_if_t, T> { using underlying = std::underlying_type_t; // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) return static_cast(static_cast(lhs) + static_cast(rhs)); @@ -281,52 +370,43 @@ constexpr auto operator+(T lhs, T rhs) template /// Subtraction for arithmetic-enabled enums. -constexpr auto operator-(T lhs, T rhs) - -> std::enable_if_t, T> { +constexpr auto operator-(T lhs, T rhs) -> std::enable_if_t, T> { using underlying = std::underlying_type_t; // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) return static_cast(static_cast(lhs) - static_cast(rhs)); } template -/// Multiplication by a value convertible to the enum's underlying type. +/// Multiplication by a scalar value. constexpr auto operator*(T lhs, U rhs) - -> std::enable_if_t && - stlab::implementation::is_convertible_to_underlying::value, - T> { + -> std::enable_if_t && stlab::is_compatible_scalar, T> { using underlying = std::underlying_type_t; // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) return static_cast(static_cast(lhs) * rhs); } template -/// Multiplication with the scalar on the left-hand side. +/// Multiplication by a scalar value. constexpr auto operator*(U lhs, T rhs) - -> std::enable_if_t && - stlab::implementation::is_convertible_to_underlying::value, - T> { + -> std::enable_if_t && stlab::is_compatible_scalar, T> { using underlying = std::underlying_type_t; // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) return static_cast(lhs * static_cast(rhs)); } template -/// Division by a value convertible to the enum's underlying type. +/// Division by a scalar value. constexpr auto operator/(T lhs, U rhs) - -> std::enable_if_t && - stlab::implementation::is_convertible_to_underlying::value, - T> { + -> std::enable_if_t && stlab::is_compatible_scalar, T> { using underlying = std::underlying_type_t; // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) return static_cast(static_cast(lhs) / rhs); } template -/// Modulo by a value convertible to the enum's underlying type. +/// Modulo by a scalar value. constexpr auto operator%(T lhs, U rhs) - -> std::enable_if_t && - stlab::implementation::is_convertible_to_underlying::value, - T> { + -> std::enable_if_t && stlab::is_compatible_scalar, T> { using underlying = std::underlying_type_t; // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) return static_cast(static_cast(lhs) % rhs); @@ -334,56 +414,46 @@ constexpr auto operator%(T lhs, U rhs) template /// Addition assignment for arithmetic-enabled enums. -constexpr auto operator+=(T& lhs, T rhs) - -> std::enable_if_t, T&> { +constexpr auto operator+=(T& lhs, T rhs) -> std::enable_if_t, T&> { return lhs = lhs + rhs; } template /// Subtraction assignment for arithmetic-enabled enums. -constexpr auto operator-=(T& lhs, T rhs) - -> std::enable_if_t, T&> { +constexpr auto operator-=(T& lhs, T rhs) -> std::enable_if_t, T&> { return lhs = lhs - rhs; } template -/// Multiplication assignment by a value convertible to the enum's underlying type. +/// Multiplication assignment by a scalar value. constexpr auto operator*=(T& lhs, U rhs) - -> std::enable_if_t && - stlab::implementation::is_convertible_to_underlying::value, - T&> { + -> std::enable_if_t && stlab::is_compatible_scalar, T&> { return lhs = lhs * rhs; } template -/// Division assignment by a value convertible to the enum's underlying type. +/// Division assignment by a scalar value. constexpr auto operator/=(T& lhs, U rhs) - -> std::enable_if_t && - stlab::implementation::is_convertible_to_underlying::value, - T&> { + -> std::enable_if_t && stlab::is_compatible_scalar, T&> { return lhs = lhs / rhs; } template -/// Modulo assignment by a value convertible to the enum's underlying type. +/// Modulo assignment by a scalar value. constexpr auto operator%=(T& lhs, U rhs) - -> std::enable_if_t && - stlab::implementation::is_convertible_to_underlying::value, - T&> { + -> std::enable_if_t && stlab::is_compatible_scalar, T&> { return lhs = lhs % rhs; } template /// Prefix increment for arithmetic-enabled enums. -constexpr auto operator++(T& lhs) - -> std::enable_if_t, T&> { +constexpr auto operator++(T& lhs) -> std::enable_if_t, T&> { return lhs += static_cast(1); } template /// Postfix increment for arithmetic-enabled enums. -constexpr auto operator++(T& lhs, int) - -> std::enable_if_t, T> { +constexpr auto operator++(T& lhs, int) -> std::enable_if_t, T> { T result = lhs; lhs += static_cast(1); return result; @@ -391,38 +461,51 @@ constexpr auto operator++(T& lhs, int) template /// Prefix decrement for arithmetic-enabled enums. -constexpr auto operator--(T& lhs) - -> std::enable_if_t, T&> { +constexpr auto operator--(T& lhs) -> std::enable_if_t, T&> { return lhs -= static_cast(1); } template /// Postfix decrement for arithmetic-enabled enums. -constexpr auto operator--(T& lhs, int) - -> std::enable_if_t, T> { +constexpr auto operator--(T& lhs, int) -> std::enable_if_t, T> { T result = lhs; lhs -= static_cast(1); return result; } +/// \} + /**************************************************************************************************/ +/// \addtogroup CommonOperations +/// \{ + +template +/// Unary minus enums. +/// For bitmask-enabled enums, this allows expressions like `e & -e` to return the least set bit. +constexpr auto operator-(T a) + -> std::enable_if_t || stlab::has_enabled_bitmask, T> { + using underlying = std::underlying_type_t; + // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) + return static_cast(-static_cast(a)); +} + template -/// Equality with nullptr for bitmask or arithmetic scoped enums; true when the value is zero. +/// Equality with nullptr for bitmask or arithmetic scoped enums; true when the value is +/// zero. constexpr auto operator==(T lhs, std::nullptr_t) - -> std::enable_if_t<(stlab::implementation::has_enabled_bitmask || - stlab::implementation::has_enabled_arithmetic) && - !stlab::implementation::is_convertible_to_underlying::value, + -> std::enable_if_t<(stlab::has_enabled_bitmask || stlab::has_enabled_arithmetic) && + !stlab::is_compatible_scalar, bool> { return !lhs; } template -/// Equality with nullptr for bitmask or arithmetic scoped enums; true when the value is zero. +/// Equality with nullptr for bitmask or arithmetic scoped enums; true when the value is +/// zero. constexpr auto operator==(std::nullptr_t, T rhs) - -> std::enable_if_t<(stlab::implementation::has_enabled_bitmask || - stlab::implementation::has_enabled_arithmetic) && - !stlab::implementation::is_convertible_to_underlying::value, + -> std::enable_if_t<(stlab::has_enabled_bitmask || stlab::has_enabled_arithmetic) && + !stlab::is_compatible_scalar, bool> { return !rhs; } @@ -430,9 +513,8 @@ constexpr auto operator==(std::nullptr_t, T rhs) template /// Inequality with nullptr for bitmask or arithmetic scoped enums. constexpr auto operator!=(T lhs, std::nullptr_t rhs) - -> std::enable_if_t<(stlab::implementation::has_enabled_bitmask || - stlab::implementation::has_enabled_arithmetic) && - !stlab::implementation::is_convertible_to_underlying::value, + -> std::enable_if_t<(stlab::has_enabled_bitmask || stlab::has_enabled_arithmetic) && + !stlab::is_compatible_scalar, bool> { return !(lhs == rhs); } @@ -440,19 +522,21 @@ constexpr auto operator!=(T lhs, std::nullptr_t rhs) template /// Inequality with nullptr for bitmask or arithmetic scoped enums. constexpr auto operator!=(std::nullptr_t lhs, T rhs) - -> std::enable_if_t<(stlab::implementation::has_enabled_bitmask || - stlab::implementation::has_enabled_arithmetic) && - !stlab::implementation::is_convertible_to_underlying::value, + -> std::enable_if_t<(stlab::has_enabled_bitmask || stlab::has_enabled_arithmetic) && + !stlab::is_compatible_scalar, bool> { return !(lhs == rhs); } template /// Logical NOT for bitmask or arithmetic enums; true when the value converts to false. -constexpr auto operator!(T lhs) -> stlab::implementation::enable_if_bitmask_or_arithmetic { +constexpr auto operator!(T lhs) + -> std::enable_if_t || stlab::has_enabled_arithmetic, bool> { return !static_cast(lhs); } +/// \} + /**************************************************************************************************/ #endif diff --git a/tests/enum_ops_tests.cpp b/tests/enum_ops_tests.cpp index 6e83910..01fa24e 100644 --- a/tests/enum_ops_tests.cpp +++ b/tests/enum_ops_tests.cpp @@ -8,7 +8,7 @@ // Test enum definitions with different underlying types and capabilities // Bitmask-only enums -enum class bitmask_flags : unsigned int { +enum class bitmask_flags : unsigned { none = 0, flag_a = 1u << 0, flag_b = 1u << 1, @@ -16,7 +16,7 @@ enum class bitmask_flags : unsigned int { all = flag_a | flag_b | flag_c }; -enum class permissions : int { +enum class permissions : unsigned { none = 0, read = 1, write = 2, @@ -178,20 +178,20 @@ TEST_CASE("Bitmask: assignment operators") { CHECK(flags == bitmask_flags::flag_a); } -TEST_CASE("Bitmask: subtraction with underlying type") { - auto result = bitmask_flags::flag_c - 1u; - // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) - CHECK(result == bitmask_flags{3u}); +TEST_CASE("Bitmask: clear_least_set") { + auto result = bitmask_flags::flag_c | bitmask_flags::flag_b; + CHECK(bitmask_flags::flag_c == clear_least_set(result)); - result = bitmask_flags::all - 1u; - // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) - CHECK(result == bitmask_flags{6u}); + auto perm_result = permissions::read | permissions::write; + CHECK(permissions::write == clear_least_set(perm_result)); +} - // Test with signed enum - auto perm_result = permissions::execute - 2; - CHECK(perm_result == permissions::write); +TEST_CASE("Bitmask: least_set") { + auto result = bitmask_flags::flag_c | bitmask_flags::flag_b; + CHECK(bitmask_flags::flag_b == least_set(result)); - static_assert(std::is_same_v); + auto perm_result = permissions::read | permissions::write; + CHECK(permissions::read == least_set(perm_result)); } /**************************************************************************************************/ From afc66043bcdcac9f1e257550b7cfd65af1d47e30 Mon Sep 17 00:00:00 2001 From: Sean Parent Date: Tue, 30 Sep 2025 18:14:14 -0700 Subject: [PATCH 2/2] Fixing tests. --- tests/enum_ops_tests.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/enum_ops_tests.cpp b/tests/enum_ops_tests.cpp index 01fa24e..73e6e9d 100644 --- a/tests/enum_ops_tests.cpp +++ b/tests/enum_ops_tests.cpp @@ -13,7 +13,8 @@ enum class bitmask_flags : unsigned { flag_a = 1u << 0, flag_b = 1u << 1, flag_c = 1u << 2, - all = flag_a | flag_b | flag_c + flag_d = 1u << 3, + all = flag_a | flag_b | flag_c | flag_d }; enum class permissions : unsigned { @@ -180,20 +181,24 @@ TEST_CASE("Bitmask: assignment operators") { TEST_CASE("Bitmask: clear_least_set") { auto result = bitmask_flags::flag_c | bitmask_flags::flag_b; - CHECK(bitmask_flags::flag_c == clear_least_set(result)); + CHECK(bitmask_flags::flag_c == (result & (result - 1))); auto perm_result = permissions::read | permissions::write; - CHECK(permissions::write == clear_least_set(perm_result)); + CHECK(permissions::write == (perm_result & (perm_result - 1))); } TEST_CASE("Bitmask: least_set") { auto result = bitmask_flags::flag_c | bitmask_flags::flag_b; - CHECK(bitmask_flags::flag_b == least_set(result)); + CHECK(bitmask_flags::flag_b == (result & -result)); auto perm_result = permissions::read | permissions::write; - CHECK(permissions::read == least_set(perm_result)); + CHECK(permissions::read == (perm_result & -perm_result)); } +TEST_CASE("Bitmask: clear_trailing_set") { + auto result = bitmask_flags::flag_d | bitmask_flags::flag_b | bitmask_flags::flag_a; + CHECK(bitmask_flags::flag_d == (result & (result + 1))); +} /**************************************************************************************************/ // ARITHMETIC OPERATIONS TESTS