diff --git a/libs/common/include/s25util/enumUtils.h b/libs/common/include/s25util/enumUtils.h index 4418692..1115dfe 100644 --- a/libs/common/include/s25util/enumUtils.h +++ b/libs/common/include/s25util/enumUtils.h @@ -4,18 +4,78 @@ #pragma once +#include #include +template +struct IsBitset : std::false_type +{}; + +template +constexpr bool IsValidBitset_v = IsBitset::value && std::is_unsigned>::value; + +template +using require_validBitset = std::enable_if_t>; + +template> +constexpr auto operator&(Enum lhs, Enum rhs) noexcept +{ + using Int = std::underlying_type_t; + return Enum(static_cast(lhs) & static_cast(rhs)); +} + +template> +constexpr auto operator|(Enum lhs, Enum rhs) noexcept +{ + using Int = std::underlying_type_t; + return Enum(static_cast(lhs) | static_cast(rhs)); +} + +template> +constexpr Enum& operator&=(Enum& lhs, Enum rhs) noexcept +{ + return lhs = lhs & rhs; +} + +template> +constexpr Enum& operator|=(Enum& lhs, Enum rhs) noexcept +{ + return lhs = lhs | rhs; +} + +namespace bitset { +template> +BOOST_ATTRIBUTE_NODISCARD constexpr Enum clear(const Enum val, const Enum flag) +{ + using Int = std::underlying_type_t; + return val & Enum(~static_cast(flag)); +} + +template> +BOOST_ATTRIBUTE_NODISCARD constexpr Enum set(const Enum val, const Enum flag, const bool state = true) +{ + return state ? (val | flag) : clear(val, flag); +} + +template> +BOOST_ATTRIBUTE_NODISCARD constexpr Enum toggle(const Enum val, const Enum flag) +{ + using Int = std::underlying_type_t; + return Enum(static_cast(val) ^ static_cast(flag)); +} + +template> +BOOST_ATTRIBUTE_NODISCARD constexpr bool isSet(const Enum val, const Enum flag) +{ + return (val & flag) == flag; +} +} // namespace bitset + /// Makes a strongly typed enum usable as a bitset -#define MAKE_BITSET_STRONG(Type) \ - inline auto operator&(Type lhs, Type rhs) \ - { \ - return Type(static_cast(lhs) & static_cast(rhs)); \ - } \ - inline auto operator|(Type lhs, Type rhs) \ - { \ - return Type(static_cast(lhs) | static_cast(rhs)); \ - } \ - /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \ - static_assert(std::is_unsigned>::value, \ +#define MAKE_BITSET_STRONG(Type) \ + template<> \ + struct IsBitset : std::true_type \ + {}; \ + /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \ + static_assert(std::is_unsigned>::value, \ #Type " must use unsigned type as the underlying type") diff --git a/tests/testEnumUtils.cpp b/tests/testEnumUtils.cpp new file mode 100644 index 0000000..ba4d3f1 --- /dev/null +++ b/tests/testEnumUtils.cpp @@ -0,0 +1,83 @@ +// Copyright (C) 2005 - 2023 Settlers Freaks (sf-team at siedler25.org) +// +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "s25util/enumUtils.h" +#include +#include + +enum class InvalidBitset : int +{ +}; +template<> +struct IsBitset : std::true_type +{}; + +enum class Bitset : unsigned +{ + None, + A = 1 << 0, + B = 1 << 1, + C = 1 << 2 +}; +MAKE_BITSET_STRONG(Bitset); + +// Check type traits +static_assert(IsBitset::value); +static_assert(!IsValidBitset_v); + +static_assert(IsBitset::value); +static_assert(IsValidBitset_v); + +BOOST_AUTO_TEST_SUITE(EnumUtils) + +BOOST_AUTO_TEST_CASE(Operators) +{ + BOOST_REQUIRE(static_cast(Bitset{}) == 0); + + { + Bitset b{}; + b = b | Bitset::A; + BOOST_TEST(static_cast(b) == 0b001u); + b |= Bitset::B; + BOOST_TEST(static_cast(b) == 0b011u); + (b |= Bitset::A) = Bitset::C; + BOOST_CHECK(b == Bitset::C); + } + + { + Bitset b = Bitset::A | Bitset::B | Bitset::C; + b = b & (Bitset::A | Bitset::B); + BOOST_TEST(static_cast(b) == 0b011u); + b &= Bitset::B; + BOOST_TEST(static_cast(b) == 0b010u); + (b &= Bitset::A) = Bitset::C; + BOOST_CHECK(b == Bitset::C); + } +} + +BOOST_AUTO_TEST_CASE(UtilityFunctions) +{ + Bitset b = Bitset::A | Bitset::C; + BOOST_TEST(bitset::isSet(b, Bitset::A)); + BOOST_TEST(bitset::isSet(b, Bitset::A | Bitset::C)); + BOOST_TEST(!bitset::isSet(b, Bitset::B)); + BOOST_TEST(!bitset::isSet(b, Bitset::B | Bitset::C)); + + b = bitset::set(b, Bitset::B /*, true */); + BOOST_TEST(static_cast(b) == 0b111u); + + b = bitset::set(b, Bitset::B, false); + BOOST_TEST(static_cast(b) == 0b101u); + + b = bitset::clear(b, Bitset::A); + BOOST_TEST(static_cast(b) == 0b100u); + + b = bitset::toggle(b, Bitset::A); + BOOST_TEST(static_cast(b) == 0b101u); + + b = bitset::toggle(b, Bitset::A | Bitset::B); + BOOST_TEST(static_cast(b) == 0b110u); +} + +BOOST_AUTO_TEST_SUITE_END()