diff --git a/libraries/application/tests/po/adapters/boost/log.tests.cpp b/libraries/application/tests/po/adapters/boost/log.tests.cpp index a67a8b328..d04c4d865 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(static_cast(cliOptions.size()), cliOptions.data(), HelpDocumentation{}, logging); REQUIRE(result); diff --git a/libraries/core/src/morpheus/core/conformance/source_location.hpp b/libraries/core/src/morpheus/core/conformance/source_location.hpp index d3f268630..b97122115 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 8a66040d5..0f107fb88 100644 --- a/libraries/core/src/morpheus/core/functional/CMakeLists.txt +++ b/libraries/core/src/morpheus/core/functional/CMakeLists.txt @@ -6,4 +6,5 @@ target_sources(MorpheusCore FILES 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..9fa387297 --- /dev/null +++ b/libraries/core/src/morpheus/core/functional/switch.hpp @@ -0,0 +1,115 @@ +#pragma once + +#include + +#include +#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){}; + +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 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>, + 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) + ); +} + +} 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..656a5506c --- /dev/null +++ b/libraries/core/tests/functional/switch.tests.cpp @@ -0,0 +1,112 @@ +#include "morpheus/core/functional/switch.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace hana = boost::hana; +using namespace hana::literals; + +namespace morpheus::functional +{ + +/* +// 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 + { + 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