From 72e501f611253366841aa1a600f9cf847ac52a0a Mon Sep 17 00:00:00 2001 From: Andreas Reischuk Date: Wed, 3 Jul 2024 14:53:49 +0200 Subject: [PATCH] Add FlagsOf implementation --- co-cpp19.qbs | 1 + src/CMakeLists.txt | 1 + src/flags19.lib/CMakeLists.txt | 3 + src/flags19.lib/flags19.lib.qbs | 9 ++ src/flags19.lib/flags19/FlagsOf.fmt.h | 31 ++++++ src/flags19.lib/flags19/FlagsOf.h | 97 +++++++++++++++++++ src/flags19.lib/flags19/FlagsOf.ostream.h | 23 +++++ src/flags19.lib/flags19/FlagsOf.test.cpp | 42 ++++++++ src/flags19.lib/flags19/FlagsOf.trait.h | 9 ++ src/flags19.lib/flags19/flags19.cmake | 29 ++++++ src/flags19.lib/flags19/flags19.qbs | 19 ++++ src/flags19.lib/flags19/flags19.tests.cmake | 11 +++ src/flags19.lib/flags19/flags19.tests.qbs | 15 +++ .../serialize19/serialize.Flags.h | 16 +++ .../serialize19/serialize19.qbs | 1 + 15 files changed, 307 insertions(+) create mode 100644 src/flags19.lib/CMakeLists.txt create mode 100644 src/flags19.lib/flags19.lib.qbs create mode 100644 src/flags19.lib/flags19/FlagsOf.fmt.h create mode 100644 src/flags19.lib/flags19/FlagsOf.h create mode 100644 src/flags19.lib/flags19/FlagsOf.ostream.h create mode 100644 src/flags19.lib/flags19/FlagsOf.test.cpp create mode 100644 src/flags19.lib/flags19/FlagsOf.trait.h create mode 100644 src/flags19.lib/flags19/flags19.cmake create mode 100644 src/flags19.lib/flags19/flags19.qbs create mode 100644 src/flags19.lib/flags19/flags19.tests.cmake create mode 100644 src/flags19.lib/flags19/flags19.tests.qbs create mode 100644 src/serialize19.lib/serialize19/serialize.Flags.h diff --git a/co-cpp19.qbs b/co-cpp19.qbs index de4f0a8a..214ed38f 100644 --- a/co-cpp19.qbs +++ b/co-cpp19.qbs @@ -11,6 +11,7 @@ Project { "src/array19.lib", "src/coro19.lib", "src/enum19.lib", + "src/flags19.lib", "src/meta19.lib", "src/lookup19.lib", "src/optional19.lib", diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b107cac4..bb12eea8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,6 +1,7 @@ add_subdirectory(array19.lib) add_subdirectory(coro19.lib) add_subdirectory(enum19.lib) +add_subdirectory(flags19.lib) add_subdirectory(lookup19.lib) add_subdirectory(meta19.lib) add_subdirectory(optional19.lib) diff --git a/src/flags19.lib/CMakeLists.txt b/src/flags19.lib/CMakeLists.txt new file mode 100644 index 00000000..fb186ef7 --- /dev/null +++ b/src/flags19.lib/CMakeLists.txt @@ -0,0 +1,3 @@ + +include(flags19/flags19.cmake) +include(flags19/flags19.tests.cmake) diff --git a/src/flags19.lib/flags19.lib.qbs b/src/flags19.lib/flags19.lib.qbs new file mode 100644 index 00000000..b31b8f86 --- /dev/null +++ b/src/flags19.lib/flags19.lib.qbs @@ -0,0 +1,9 @@ + +Project { + name: "flags19.lib" + + references: [ + "flags19/flags19.qbs", + "flags19/flags19.tests.qbs", + ] +} diff --git a/src/flags19.lib/flags19/FlagsOf.fmt.h b/src/flags19.lib/flags19/FlagsOf.fmt.h new file mode 100644 index 00000000..97dd8b2e --- /dev/null +++ b/src/flags19.lib/flags19/FlagsOf.fmt.h @@ -0,0 +1,31 @@ +#pragma once +#include "FlagsOf.h" +#include "enum19/Enum.names.h" + +/// noto: for this header you need fmt library (not included as library dependency) +#include +#include + +/// adds enum support for fmt +template struct fmt::formatter, Char> { + using T = flags19::FlagsOf; + using Value = typename T::Value; + constexpr auto parse(fmt::basic_format_parse_context& ctx) { return ctx.begin(); } + + template auto format(const T& v, FormatCtx& ctx) const { + // auto underlying = static_cast>(v); + using namespace std::string_view_literals; + auto printed = false; + for (auto& member : enum19::meta_enum_for.members) { + if (!v[member.value]) continue; + fmt::format_to(ctx.out(), "{}{}", (printed ? "|"sv : ""sv), enum19::valueName(member.value)); + printed = true; + } + return fmt::format_to( + ctx.out(), + "{} ({:0{}b})", + (printed ? ""sv : "<>"sv), + static_cast(v), + enum19::max_underlying_value_of); + } +}; diff --git a/src/flags19.lib/flags19/FlagsOf.h b/src/flags19.lib/flags19/FlagsOf.h new file mode 100644 index 00000000..9455d997 --- /dev/null +++ b/src/flags19.lib/flags19/FlagsOf.h @@ -0,0 +1,97 @@ +#pragma once +#include "enum19/Enum.h" +#include "enum19/Enum.max.h" + +#include // int64_t, uint64_t + +namespace flags19 { + +using enum19::HasMetaEnum; +using enum19::max_underlying_value_of; +using enum19::meta_enum_for; + +namespace details { + +template constexpr auto storageTypeForMaxBit() { + if constexpr (maxBit <= sizeof(uint8_t)) { + return uint8_t{}; + } + else if constexpr (maxBit <= sizeof(uint16_t)) { + return uint16_t{}; + } + else if constexpr (maxBit <= sizeof(uint32_t)) { + return uint32_t{}; + } + else if constexpr (maxBit <= sizeof(uint64_t)) { + return uint64_t{}; + } + else { + static_assert(maxBit > sizeof(uint64_t), "not supported right now"); + } +} + +} // namespace details + +template struct FlagsOf { + using UnderlyingBit = std::underlying_type_t; + using Value = decltype(details::storageTypeForMaxBit>()); + + constexpr FlagsOf() = default; + explicit constexpr FlagsOf(Value const& value) : m_value{value} {} + + template requires((sizeof...(Args) > 0) && ... && std::is_same_v) + explicit constexpr FlagsOf(Args... args) : FlagsOf{((1U << static_cast(args)) | ...)} {} + + auto operator==(FlagsOf const&) const -> bool = default; + + explicit operator Value() const { return m_value; } + + [[nodiscard]] constexpr auto operator[](Enum bit) const noexcept -> bool { + return 0U != (m_value & (1U << static_cast(bit))); + } + + template requires((sizeof...(Args) > 0) && ... && std::is_same_v) + constexpr auto allOf(Args... args) const -> bool { + auto const mask = ((1U << static_cast(args)) | ...); + return mask == (m_value & mask); + } + + template requires((sizeof...(Args) > 0) && ... && std::is_same_v) + constexpr auto someOf(Args... args) const -> bool { + auto const mask = ((1U << static_cast(args)) | ...); + return 0U != (m_value & mask); + } + + template requires((sizeof...(Args) > 0) && ... && std::is_same_v) + constexpr auto noneOf(Args... args) const -> bool { + auto const mask = ((1U << static_cast(args)) | ...); + return 0U == (m_value & mask); + } + + constexpr void resetAll() { m_value = {}; } + + template requires((sizeof...(Args) > 0) && ... && std::is_same_v) + constexpr void set(Args... args) { + m_value |= ((1U << static_cast(args)) | ...); + } + + template requires((sizeof...(Args) > 0) && ... && std::is_same_v) + constexpr void reset(Args... args) { + m_value &= ~static_cast(((1U << static_cast(args)) | ...)); + } + + template requires((sizeof...(Args) > 0) && ... && std::is_same_v) + constexpr void toggle(Args... args) { + m_value ^= ((1U << static_cast(args)) | ...); + } + + constexpr auto operator|(FlagsOf const& other) const -> FlagsOf { return FlagsOf{m_value | other.m_value}; } + constexpr auto operator&(FlagsOf const& other) const -> FlagsOf { return FlagsOf{m_value & other.m_value}; } + constexpr auto operator|=(FlagsOf const& other) -> FlagsOf& { return m_value |= other.m_value, *this; } + constexpr auto operator&=(FlagsOf const& other) -> FlagsOf& { return m_value &= other.m_value, *this; } + +private: + Value m_value{}; +}; + +} // namespace flags19 diff --git a/src/flags19.lib/flags19/FlagsOf.ostream.h b/src/flags19.lib/flags19/FlagsOf.ostream.h new file mode 100644 index 00000000..65a01472 --- /dev/null +++ b/src/flags19.lib/flags19/FlagsOf.ostream.h @@ -0,0 +1,23 @@ +#pragma once +#include "FlagsOf.h" + +#include +#include +#include +#include + +namespace flags19 { + +template auto operator<<(std::ostream& out, FlagsOf const& flags) -> std::ostream& { + using namespace std::string_view_literals; + auto printed = false; + for (auto& member : enum19::meta_enum_for.members) { + if (!flags[member.value]) continue; + out << (printed ? "|"sv : ""sv) << enum19::valueName(member.value); + printed = true; + } + return out << (printed ? " ("sv : "<> ("sv) + << std::bitset>{static_cast::Value>(flags)} << ')'; +} + +} // namespace flags19 diff --git a/src/flags19.lib/flags19/FlagsOf.test.cpp b/src/flags19.lib/flags19/FlagsOf.test.cpp new file mode 100644 index 00000000..89cba6cf --- /dev/null +++ b/src/flags19.lib/flags19/FlagsOf.test.cpp @@ -0,0 +1,42 @@ +#include "FlagsOf.h" + +#include "FlagsOf.ostream.h" + +#include + +using namespace flags19; + +namespace my_test { + +ENUM19(CheckBit, uint8_t, Ordered, Packaged, Delivered, Received, Complained, ReturnLabeled, ReturnReceived); +using CheckBits = FlagsOf; + +} // namespace my_test + +TEST(Flags, example) { + using enum my_test::CheckBit; + constexpr auto checks = my_test::CheckBits{Ordered, Delivered}; + + static_assert(checks[Ordered]); + static_assert(checks[Delivered]); + static_assert(!checks[Packaged]); + static_assert(!checks[ReturnReceived]); + + static_assert(checks.allOf(Delivered)); + static_assert(!checks.allOf(Delivered, Received)); + + static_assert(checks.someOf(Delivered, Received)); + + static_assert(checks.noneOf(Complained, Received)); + + auto mutChecks = checks; + mutChecks.set(Packaged); + EXPECT_TRUE(mutChecks[Packaged]); + + mutChecks.reset(Delivered, Received); + EXPECT_FALSE(mutChecks[Delivered]); + EXPECT_TRUE(mutChecks.noneOf(Delivered, Received)); + + mutChecks.toggle(Packaged, Delivered); + EXPECT_EQ(mutChecks, checks); +} diff --git a/src/flags19.lib/flags19/FlagsOf.trait.h b/src/flags19.lib/flags19/FlagsOf.trait.h new file mode 100644 index 00000000..65684e5a --- /dev/null +++ b/src/flags19.lib/flags19/FlagsOf.trait.h @@ -0,0 +1,9 @@ +#pragma once +#include "FlagsOf.h" + +namespace flags19 { + +template constexpr auto is_flags_of = false; +template constexpr auto is_flags_of> = true; + +} // namespace flags19 diff --git a/src/flags19.lib/flags19/flags19.cmake b/src/flags19.lib/flags19/flags19.cmake new file mode 100644 index 00000000..e752bf18 --- /dev/null +++ b/src/flags19.lib/flags19/flags19.cmake @@ -0,0 +1,29 @@ +add_library(flags19 INTERFACE) +target_link_libraries(flags19 + INTERFACE CoCpp19::enum19 +) +target_include_directories(flags19 + INTERFACE + $ + $ +) +file(GLOB flags19_headers "${CMAKE_CURRENT_LIST_DIR}/*.h") +target_sources(flags19 + INTERFACE FILE_SET public_headers + TYPE HEADERS + FILES ${flags19_headers} +) + +add_library(CoCpp19::flags19 ALIAS flags19) +install(TARGETS flags19 + EXPORT flags19Targets + FILE_SET public_headers + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + COMPONENT "flags19" +) +install(EXPORT flags19Targets + FILE "CoCpp19-flags19-targets.cmake" + NAMESPACE "CoCpp19::" + DESTINATION ${COCPP19_CMAKE_CONFIG_DESTINATION} + COMPONENT "flags19" +) diff --git a/src/flags19.lib/flags19/flags19.qbs b/src/flags19.lib/flags19/flags19.qbs new file mode 100644 index 00000000..7d0d7296 --- /dev/null +++ b/src/flags19.lib/flags19/flags19.qbs @@ -0,0 +1,19 @@ + +Product { + Depends { name: "cpp" } + Depends { name: "enum19" } + Depends { name: "fmt"; required: false } + + Export { + Depends { name: "cpp" } + cpp.includePaths: [".."] + Depends { name: "enum19" } + } + + files: [ + "FlagsOf.fmt.h", + "FlagsOf.h", + "FlagsOf.ostream.h", + "FlagsOf.trait.h", + ] +} diff --git a/src/flags19.lib/flags19/flags19.tests.cmake b/src/flags19.lib/flags19/flags19.tests.cmake new file mode 100644 index 00000000..bd7a4c2e --- /dev/null +++ b/src/flags19.lib/flags19/flags19.tests.cmake @@ -0,0 +1,11 @@ + +add_executable(flags19-test) +file(GLOB flags19_test_sources "${CMAKE_CURRENT_LIST_DIR}/*.test.cpp") +target_sources(flags19-test + PRIVATE ${flags19_test_sources} +) +target_link_libraries(flags19-test + PRIVATE GTest::gtest_main + PRIVATE CoCpp19::flags19 +) +add_test(NAME flags19 COMMAND flags19-test) diff --git a/src/flags19.lib/flags19/flags19.tests.qbs b/src/flags19.lib/flags19/flags19.tests.qbs new file mode 100644 index 00000000..ed478e55 --- /dev/null +++ b/src/flags19.lib/flags19/flags19.tests.qbs @@ -0,0 +1,15 @@ + +Application { + name: "flags19.tests" + condition: googletest.present + + consoleApplication: true + type: ["application", "autotest"] + + Depends { name: "flags19" } + Depends { name: "googletest" } + + files: [ + "FlagsOf.test.cpp", + ] +} diff --git a/src/serialize19.lib/serialize19/serialize.Flags.h b/src/serialize19.lib/serialize19/serialize.Flags.h new file mode 100644 index 00000000..e37fc362 --- /dev/null +++ b/src/serialize19.lib/serialize19/serialize.Flags.h @@ -0,0 +1,16 @@ +#pragma once +#include "flags19/FlagsOf.h" // requires variant19 +#include "serialize.h" + +namespace serialize19 { + +template void serialize(A& a, flags19::FlagsOf& flags) { + using Value = typename flags19::FlagsOf::Value; + auto value = static_cast(flags); + serialize(a, value); + if constexpr (A::mode == ArchiveMode::Read) { + flags = flags19::FlagsOf{value}; + } +} + +} // namespace serialize19 diff --git a/src/serialize19.lib/serialize19/serialize19.qbs b/src/serialize19.lib/serialize19/serialize19.qbs index 202bd158..080f7203 100644 --- a/src/serialize19.lib/serialize19/serialize19.qbs +++ b/src/serialize19.lib/serialize19/serialize19.qbs @@ -43,6 +43,7 @@ Product { "serialize.Array.h", "serialize.BufferSlice.h", "serialize.DynamicArrayOf.h", + "serialize.Flags.h", "serialize.None.h", "serialize.Optional.h", "serialize.PackedOptional.h",