From 2e1ecf0f52f3511817294e86ba18d77eac35e2d8 Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sat, 8 Jul 2023 18:15:04 +0100 Subject: [PATCH 1/7] Compile type switch statement --- .../tests/po/adapters/boost/log.tests.cpp | 4 +- .../core/src/morpheus/core/base/debugging.cpp | 8 +- .../src/morpheus/core/base/unreachable.hpp | 22 ++-- .../core/conformance/source_location.hpp | 4 + .../morpheus/core/functional/CMakeLists.txt | 1 + .../src/morpheus/core/functional/switch.hpp | 114 ++++++++++++++++++ .../core/tests/functional/CMakeLists.txt | 1 + .../core/tests/functional/switch.tests.cpp | 25 ++++ 8 files changed, 166 insertions(+), 13 deletions(-) create mode 100644 libraries/core/src/morpheus/core/functional/switch.hpp create mode 100644 libraries/core/tests/functional/switch.tests.cpp diff --git a/libraries/application/tests/po/adapters/boost/log.tests.cpp b/libraries/application/tests/po/adapters/boost/log.tests.cpp index 08269e74a..810107784 100644 --- a/libraries/application/tests/po/adapters/boost/log.tests.cpp +++ b/libraries/application/tests/po/adapters/boost/log.tests.cpp @@ -38,7 +38,7 @@ TEST_CASE_METHOD(BoostLogFixture, "Test parsing of boost log types as program op SECTION("Ensure valid value parse correctly") { - auto getLogLevel = [](std::string_view param) + auto const getLogLevel = [](std::string_view param) { Logging logging{}; std::array cliOptions = { "dummyProgram.exe", "--log-level", param.data() }; @@ -68,7 +68,7 @@ TEST_CASE_METHOD(BoostLogFixture, "Test parsing of boost log types as program op } SECTION("Ensure invalid value parse correctly") { - std::array cliOptions = { "dummyProgram.exe", "--log-level", "invalid"}; + std::array const cliOptions = { "dummyProgram.exe", "--log-level", "invalid"}; Logging logging; auto const result = parseProgramOptions(cliOptions.size(), cliOptions.data(), HelpDocumentation{}, logging); REQUIRE(result); diff --git a/libraries/core/src/morpheus/core/base/debugging.cpp b/libraries/core/src/morpheus/core/base/debugging.cpp index f53f795a7..6bb74be5e 100644 --- a/libraries/core/src/morpheus/core/base/debugging.cpp +++ b/libraries/core/src/morpheus/core/base/debugging.cpp @@ -24,17 +24,17 @@ void breakpoint() noexcept #if (MORPHEUS_BUILD_PLATFORM == MORPHEUS_TARGET_PLATFORM_PC_WINDOWS) __debugbreak(); - MORPHEUS_UNREACHABLE; + unreachable(); // https://github.com/scottt/debugbreak/issues/24 #elif (MORPHEUS_COMPILER == MORPHEUS_CLANG_COMPILER) && defined(__has_builtin) && __has_builtin(__builtin_debugtrap) __builtin_debugtrap(); - MORPHEUS_UNREACHABLE; + unreachable(); #elif (MORPHEUS_BUILD_PLATFORM == MORPHEUS_TARGET_PLATFORM_LINUX) || (MORPHEUS_BUILD_PLATFORM == MORPHEUS_TARGET_PLATFORM_APPLE) raise(SIGTRAP); - MORPHEUS_UNREACHABLE; + unreachable(); #else // *(int*)0 = 0; - // MORPHEUS_UNREACHABLE; + // unreachable(); #endif } diff --git a/libraries/core/src/morpheus/core/base/unreachable.hpp b/libraries/core/src/morpheus/core/base/unreachable.hpp index 1ad7a1eb0..510400804 100644 --- a/libraries/core/src/morpheus/core/base/unreachable.hpp +++ b/libraries/core/src/morpheus/core/base/unreachable.hpp @@ -1,15 +1,23 @@ #pragma once #include +#include -/// \def MORPHEUS_UNREACHABLE -/// Macro which can be used to signify to the compiler that +/// Function to signify to the compiler that /// - The programmer knows code following can never be executed (so dead code elimination can be performed). /// - Linearize code (remove branches) by knowing that a path is cold (similar to calling [[noreturn]] function). -#if (MORPHEUS_IS_VISUALSTUDIO_COMPATIBLE_COMPILER) - #define MORPHEUS_UNREACHABLE __assume(0) -#elif (MORPHEUS_IS_GCC_COMPATIBLE_COMPILER) - #define MORPHEUS_UNREACHABLE __builtin_unreachable() +/// \note +/// Conformance wrapper for https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0627r6.pdf +// clang-format off +#if (__cpp_lib_unreachable >= 202202L) +[[noreturn]] inline void unreachable() { std::unreachable(); } #else - #define MORPHEUS_UNREACHABLE void() + #if (MORPHEUS_IS_VISUALSTUDIO_COMPATIBLE_COMPILER) + [[noreturn]] inline void unreachable() { __assume(false); } + #elif (MORPHEUS_IS_GCC_COMPATIBLE_COMPILER) + [[noreturn]] inline void unreachable() { __builtin_unreachable(); } + #else + [[noreturn]] inline void unreachable() {} + #endif #endif +// clang-format on diff --git a/libraries/core/src/morpheus/core/conformance/source_location.hpp b/libraries/core/src/morpheus/core/conformance/source_location.hpp index 983ef073a..17338963c 100644 --- a/libraries/core/src/morpheus/core/conformance/source_location.hpp +++ b/libraries/core/src/morpheus/core/conformance/source_location.hpp @@ -4,6 +4,10 @@ #include #endif +/// \namespace morpheus::sl_ns +/// Conformance namespace abstracting the underlying source_location while compilers do not offer uniform support. +/// When support is missing falls back to boost::source_location. + // clang-format off #if (__cpp_lib_source_location >= 201907L) diff --git a/libraries/core/src/morpheus/core/functional/CMakeLists.txt b/libraries/core/src/morpheus/core/functional/CMakeLists.txt index 60a3a78e0..a78a9f2a1 100644 --- a/libraries/core/src/morpheus/core/functional/CMakeLists.txt +++ b/libraries/core/src/morpheus/core/functional/CMakeLists.txt @@ -2,4 +2,5 @@ target_sources(MorpheusCore PUBLIC function_ref.hpp overload.hpp + switch.hpp ) diff --git a/libraries/core/src/morpheus/core/functional/switch.hpp b/libraries/core/src/morpheus/core/functional/switch.hpp new file mode 100644 index 000000000..c7bd0595b --- /dev/null +++ b/libraries/core/src/morpheus/core/functional/switch.hpp @@ -0,0 +1,114 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include + +namespace morpheus::functional +{ + +/// \struct CaseList +/// Typelist to hold cases for the switch +template +struct CaseList +{ +}; + +constexpr auto switchCaseElement(auto case_, auto f) {return f(case_); } +constexpr auto switchCondition(auto case_) { return switchCaseElement(case_, boost::hana::first); } +constexpr auto switchTag(auto case_) { return switchCaseElement(case_, boost::hana::second); } + +#ifndef MORPHEUS_SWITCH_MAX + #define MORPHEUS_SWITCH_MAX 64 +#endif + +inline constexpr auto defaultCase = [](auto unhandledValue){}; + +template> +using SwitchCase = boost::hana::pair, T>; + +namespace detail { + + +#define MORPHEUS_SWITCH_CASE(Z,N,_) \ + case decltype(switchCondition(boost::mp11::mp_at_c{}))::value: \ + return std::forward(caseHander)(decltype(switchTag(boost::mp11::mp_at_c{})){}); + +template +Result invokeDefaultHandler(Condition condition, DefaultHandler defaultHandler) +{ + if constexpr (!std::is_void_v && std::is_void_v>) + { + defaultHandler(condition); + unreachable(); + } + else + return defaultHandler(condition); +} + +#define MORPHEUS_SWITCH_OVERLOAD(Z, N, _) \ + template \ + constexpr Result switch_(std::integral_constant, Condition const condition, CaseList const cases, CaseHandler&& caseHander, \ + DefaultCase&& defaultCase) \ + { \ + switch (condition) { \ + BOOST_PP_REPEAT_##Z(N, MORPHEUS_SWITCH_CASE, ~) \ + } \ + return invokeDefaultHandler(condition, defaultCase); \ + } + +BOOST_PP_REPEAT(MORPHEUS_SWITCH_MAX, MORPHEUS_SWITCH_OVERLOAD, ~) +#undef SWITCH_CASE +#undef SWITCH_OVERLOAD + +template struct SwitchCommonReference { + using type = std::conditional_t<(std::is_rvalue_reference_v && ...), std::add_rvalue_reference_t>, + std::conditional_t<(std::is_reference_v && ...), std::add_lvalue_reference_t>, + std::common_type_t>>; +}; + +template +auto switchResult(CaseList caseList) +{ + constexpr std::size_t Size = boost::mp11::mp_size::value; + if constexpr (Size == 0u) + return std::invoke_result{}; + else + return [&](std::index_sequence) + { + return SwitchCommonReference< + std::invoke_result_t{}))>...>{}; + }(std::make_index_sequence{}); +} + +template +using SwitchResult = typename decltype(switchResult(std::declval()))::type; + +} + +/// Compile time generation of switch statements. Maps a given list of values This is +/// +template +constexpr detail::SwitchResult switch_( + Condition const condition, /// The switch conditional value to be executed. + CaseList const caseList, /// List of case predicates. + CaseHandler&& caseHander, /// Handler for case statement bodies. + DefaultCase&& defaultCase = DefaultCase{} /// Handler for the default statement of the switch. +) +{ + constexpr std::size_t Size = boost::mp11::mp_size::value; + static_assert(Size < MORPHEUS_SWITCH_MAX, "Increase MORPHEUS_SWITCH_MAX"); + using Result = detail::SwitchResult; + return detail::switch_ + (std::integral_constant{}, condition, caseList, + std::forward(caseHander), + std::forward(defaultCase) + ); +} + +} \ No newline at end of file diff --git a/libraries/core/tests/functional/CMakeLists.txt b/libraries/core/tests/functional/CMakeLists.txt index 9ffdd0e57..129901a34 100644 --- a/libraries/core/tests/functional/CMakeLists.txt +++ b/libraries/core/tests/functional/CMakeLists.txt @@ -1,4 +1,5 @@ target_sources(MorpheusCoreTests PRIVATE function_ref.tests.cpp + switch.tests.cpp ) diff --git a/libraries/core/tests/functional/switch.tests.cpp b/libraries/core/tests/functional/switch.tests.cpp new file mode 100644 index 000000000..6b40d42e6 --- /dev/null +++ b/libraries/core/tests/functional/switch.tests.cpp @@ -0,0 +1,25 @@ +#include "morpheus/core/functional/switch.hpp" + +#include + +#include + +namespace morpheus::functional +{ + +TEST_CASE("", "[morpheus.functional.switch.enum]") +{ + enum class Case + { + A, + B, + C, + D + }; + using Cases = CaseList, SwitchCase, SwitchCase, SwitchCase>; + STATIC_REQUIRE(boost::mp11::mp_size::value == magic_enum::enum_count()); + magic_enum::enum_for_each([](auto const caseToCall) + { switch_(caseToCall, Cases{}, [&](auto const executedCase) { REQUIRE(caseToCall == executedCase); }); }); +} + +} // namespace morpheus::functional \ No newline at end of file From bf36847a39a624460bb2e5fadbca594d4fa896b4 Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sat, 8 Jul 2023 18:19:00 +0100 Subject: [PATCH 2/7] Correct boost hana includes --- .../core/src/morpheus/core/functional/switch.hpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/libraries/core/src/morpheus/core/functional/switch.hpp b/libraries/core/src/morpheus/core/functional/switch.hpp index c7bd0595b..09b787dc9 100644 --- a/libraries/core/src/morpheus/core/functional/switch.hpp +++ b/libraries/core/src/morpheus/core/functional/switch.hpp @@ -2,7 +2,9 @@ #include +#include #include +#include #include #include #include @@ -19,7 +21,7 @@ struct CaseList { }; -constexpr auto switchCaseElement(auto case_, auto f) {return f(case_); } +constexpr auto switchCaseElement(auto case_, auto f) { return f(case_); } constexpr auto switchCondition(auto case_) { return switchCaseElement(case_, boost::hana::first); } constexpr auto switchTag(auto case_) { return switchCaseElement(case_, boost::hana::second); } @@ -32,11 +34,10 @@ inline constexpr auto defaultCase = [](auto unhandledValue){}; template> using SwitchCase = boost::hana::pair, T>; -namespace detail { +namespace detail { - -#define MORPHEUS_SWITCH_CASE(Z,N,_) \ - case decltype(switchCondition(boost::mp11::mp_at_c{}))::value: \ +#define MORPHEUS_SWITCH_CASE(Z, N, _) \ + case decltype(switchCondition(boost::mp11::mp_at_c{}))::value: \ return std::forward(caseHander)(decltype(switchTag(boost::mp11::mp_at_c{})){}); template From 02ffda950764a01de7c04bc47ef3aad93e9054fe Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sat, 8 Jul 2023 18:59:24 +0100 Subject: [PATCH 3/7] Update magic enum --- conanfile.py | 2 +- libraries/core/src/morpheus/core/base/unreachable.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/conanfile.py b/conanfile.py index dd7acc35c..32f1f1596 100644 --- a/conanfile.py +++ b/conanfile.py @@ -73,7 +73,7 @@ class Morpheus(ConanFile): "glbinding/3.1.0", "glew/2.2.0", "gtest/1.13.0", - "magic_enum/0.8.2", + "magic_enum/0.9.2", "ms-gsl/4.0.0", "rapidjson/cci.20220822", "range-v3/0.12.0", diff --git a/libraries/core/src/morpheus/core/base/unreachable.hpp b/libraries/core/src/morpheus/core/base/unreachable.hpp index 510400804..3a8e8b558 100644 --- a/libraries/core/src/morpheus/core/base/unreachable.hpp +++ b/libraries/core/src/morpheus/core/base/unreachable.hpp @@ -10,7 +10,7 @@ /// Conformance wrapper for https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0627r6.pdf // clang-format off #if (__cpp_lib_unreachable >= 202202L) -[[noreturn]] inline void unreachable() { std::unreachable(); } + [[noreturn]] inline void unreachable() { std::unreachable(); } #else #if (MORPHEUS_IS_VISUALSTUDIO_COMPATIBLE_COMPILER) [[noreturn]] inline void unreachable() { __assume(false); } From 48cbe45ceb940e376e02c3660eccc512929022e3 Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sun, 9 Jul 2023 12:06:28 +0100 Subject: [PATCH 4/7] Fix macros --- libraries/core/src/morpheus/core/functional/switch.hpp | 4 ++-- .../core/src/morpheus/core/serialisation/json_reader.cpp | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/libraries/core/src/morpheus/core/functional/switch.hpp b/libraries/core/src/morpheus/core/functional/switch.hpp index 09b787dc9..78c0bb669 100644 --- a/libraries/core/src/morpheus/core/functional/switch.hpp +++ b/libraries/core/src/morpheus/core/functional/switch.hpp @@ -64,8 +64,8 @@ Result invokeDefaultHandler(Condition condition, DefaultHandler defaultHandler) } BOOST_PP_REPEAT(MORPHEUS_SWITCH_MAX, MORPHEUS_SWITCH_OVERLOAD, ~) -#undef SWITCH_CASE -#undef SWITCH_OVERLOAD +#undef MORPHEUS_SWITCH_CASE +#undef MORPHEUS_SWITCH_OVERLOAD template struct SwitchCommonReference { using type = std::conditional_t<(std::is_rvalue_reference_v && ...), std::add_rvalue_reference_t>, diff --git a/libraries/core/src/morpheus/core/serialisation/json_reader.cpp b/libraries/core/src/morpheus/core/serialisation/json_reader.cpp index c47e6b3a5..ce41b1a81 100644 --- a/libraries/core/src/morpheus/core/serialisation/json_reader.cpp +++ b/libraries/core/src/morpheus/core/serialisation/json_reader.cpp @@ -4,8 +4,6 @@ #include "morpheus/core/conformance/format.hpp" #include "morpheus/core/serialisation/read_serialiser.hpp" -// #include - namespace morpheus::serialisation { From fb879ff2eced2b96c280f14951cac5e159a00111 Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Mon, 10 Jul 2023 21:08:53 +0100 Subject: [PATCH 5/7] Work in progress testing for functional switch --- .../core/tests/functional/switch.tests.cpp | 95 ++++++++++++++++++- 1 file changed, 91 insertions(+), 4 deletions(-) diff --git a/libraries/core/tests/functional/switch.tests.cpp b/libraries/core/tests/functional/switch.tests.cpp index 6b40d42e6..e0460d028 100644 --- a/libraries/core/tests/functional/switch.tests.cpp +++ b/libraries/core/tests/functional/switch.tests.cpp @@ -1,13 +1,93 @@ #include "morpheus/core/functional/switch.hpp" +#include +#include +#include +#include +#include +#include #include -#include +#include + +namespace hana = boost::hana; +using namespace hana::literals; namespace morpheus::functional { -TEST_CASE("", "[morpheus.functional.switch.enum]") +/* +// clang-format off +struct ReturnLValueReference +{ + bool a; + char b; + int c; + float d; + + bool& operator()(bool) { return a; } + char& operator()(char) { return b; } + int& operator()(int) { return c; } + float& operator()(float) { return d; } +}; + +// struct ReturnRValueReference +// { +// std::tuple values; + +// bool&& operator()(bool) { return std::get<0>(values); } +// char&& operator()(char) { return std::get<1>(values); } +// int&& operator()(int) { return std::get<2>(values); } +// float&& operator()(float) { return std::get<3>(values); } +// }; + +// clang-format on + +TEST_CASE("Verify switch cases returning a value", "[morpheus.functional.switch.value]") +{ + using Cases = CaseList, SwitchCase, SwitchCase, SwitchCase>; + STATIC_REQUIRE(boost::mp11::mp_size::value == 4); + for (int i = 0; i < boost::mp11::mp_size::value; ++i) { + auto const result = switch_(i, Cases{}, + [](auto const caseValue) + { + return caseValue; + }); + REQUIRE(result == i); + } +} + +TEST_CASE("Verify switch cases returning a lvalue-reference", "[morpheus.functional.switch.lvalue_reference]") +{ + // auto types = boost::hana::tuple_t; + // auto caseValues = [](std::index_sequence) + // { + // return boost::hana::tuple_c; + // }(std::make_index_sequence()); + // auto caseParams = boost::hana::zip(types, caseValues); + // auto cases = boost::hana::transform(caseParams, + // [](auto const params) + // { + // return SwitchCase; + // }); + + // using CaseTypes = std::tuple; + // using CaseValues = boost::mp11::mp_iota_c<4>; + // using Case = boost::hana::transform + // using Cases = boost::mp11::mp_transform< + + using Cases = CaseList, SwitchCase, SwitchCase, SwitchCase>; + STATIC_REQUIRE(boost::mp11::mp_size::value == 4); + + ReturnLValueReference expected; + auto const& result = switch_(3, Cases{}, std::ref(expected)); + REQUIRE(&result == &expected.d); +} + +TEST_CASE("Verify switch cases returning a rvalue-reference", "[morpheus.functional.switch.rvalue_reference]") {} +*/ + +TEST_CASE("Enumerate switch cases for each enum entry", "[morpheus.functional.switch.enum]") { enum class Case { @@ -18,8 +98,15 @@ TEST_CASE("", "[morpheus.functional.switch.enum]") }; using Cases = CaseList, SwitchCase, SwitchCase, SwitchCase>; STATIC_REQUIRE(boost::mp11::mp_size::value == magic_enum::enum_count()); - magic_enum::enum_for_each([](auto const caseToCall) - { switch_(caseToCall, Cases{}, [&](auto const executedCase) { REQUIRE(caseToCall == executedCase); }); }); + magic_enum::enum_for_each( + [](auto const caseToCall) + { + switch_(caseToCall, Cases{}, + [&](auto const executedCase) + { + REQUIRE(caseToCall == executedCase); + }); + }); } } // namespace morpheus::functional \ No newline at end of file From b4c4a18e9464e05b4820b36c8ab4484fbde79b9d Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sat, 20 Jan 2024 17:34:29 +0000 Subject: [PATCH 6/7] Update unreachable path --- .../src/morpheus/core/functional/switch.hpp | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/libraries/core/src/morpheus/core/functional/switch.hpp b/libraries/core/src/morpheus/core/functional/switch.hpp index 78c0bb669..ea7f0cde5 100644 --- a/libraries/core/src/morpheus/core/functional/switch.hpp +++ b/libraries/core/src/morpheus/core/functional/switch.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include @@ -15,10 +15,10 @@ namespace morpheus::functional { /// \struct CaseList -/// Typelist to hold cases for the switch +/// Typelist to hold cases for the switch template -struct CaseList -{ +struct CaseList +{ }; constexpr auto switchCaseElement(auto case_, auto f) { return f(case_); } @@ -71,7 +71,7 @@ template struct SwitchCommonReference { using type = std::conditional_t<(std::is_rvalue_reference_v && ...), std::add_rvalue_reference_t>, std::conditional_t<(std::is_reference_v && ...), std::add_lvalue_reference_t>, std::common_type_t>>; -}; +}; template auto switchResult(CaseList caseList) @@ -92,24 +92,24 @@ using SwitchResult = typename decltype(switchResult constexpr detail::SwitchResult switch_( Condition const condition, /// The switch conditional value to be executed. CaseList const caseList, /// List of case predicates. - CaseHandler&& caseHander, /// Handler for case statement bodies. - DefaultCase&& defaultCase = DefaultCase{} /// Handler for the default statement of the switch. + CaseHandler&& caseHander, /// Handler for case statement bodies. + DefaultCase&& defaultCase = DefaultCase{} /// Handler for the default statement of the switch. ) { constexpr std::size_t Size = boost::mp11::mp_size::value; static_assert(Size < MORPHEUS_SWITCH_MAX, "Increase MORPHEUS_SWITCH_MAX"); using Result = detail::SwitchResult; return detail::switch_ - (std::integral_constant{}, condition, caseList, - std::forward(caseHander), + (std::integral_constant{}, condition, caseList, + std::forward(caseHander), std::forward(defaultCase) ); } -} \ No newline at end of file +} From cdbbcb7c2531a6666384074e74aaff3b6ef3cebe Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sat, 24 May 2025 19:34:37 +0100 Subject: [PATCH 7/7] Minor fixes --- libraries/core/src/morpheus/core/functional/switch.hpp | 2 +- libraries/core/tests/functional/switch.tests.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/core/src/morpheus/core/functional/switch.hpp b/libraries/core/src/morpheus/core/functional/switch.hpp index ea7f0cde5..9fa387297 100644 --- a/libraries/core/src/morpheus/core/functional/switch.hpp +++ b/libraries/core/src/morpheus/core/functional/switch.hpp @@ -29,7 +29,7 @@ constexpr auto switchTag(auto case_) { return switchCaseElement(case_, boost::ha #define MORPHEUS_SWITCH_MAX 64 #endif -inline constexpr auto defaultCase = [](auto unhandledValue){}; +inline constexpr auto defaultCase = [](auto){}; template> using SwitchCase = boost::hana::pair, T>; diff --git a/libraries/core/tests/functional/switch.tests.cpp b/libraries/core/tests/functional/switch.tests.cpp index e0460d028..656a5506c 100644 --- a/libraries/core/tests/functional/switch.tests.cpp +++ b/libraries/core/tests/functional/switch.tests.cpp @@ -6,7 +6,7 @@ #include #include #include -#include +#include #include