Skip to content
4 changes: 2 additions & 2 deletions libraries/application/tests/po/adapters/boost/log.tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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() };
Expand Down Expand Up @@ -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<int>(cliOptions.size()), cliOptions.data(), HelpDocumentation{}, logging);
REQUIRE(result);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
#include <source_location>
#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)

Expand Down
1 change: 1 addition & 0 deletions libraries/core/src/morpheus/core/functional/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ target_sources(MorpheusCore
FILES
function_ref.hpp
overload.hpp
switch.hpp
)
115 changes: 115 additions & 0 deletions libraries/core/src/morpheus/core/functional/switch.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#pragma once

#include <morpheus/core/conformance/unreachable.hpp>

#include <boost/hana/first.hpp>
#include <boost/hana/pair.hpp>
#include <boost/hana/second.hpp>
#include <boost/mp11.hpp>
#include <boost/preprocessor/repetition.hpp>
#include <concepts>
#include <tuple>
#include <type_traits>

namespace morpheus::functional
{

/// \struct CaseList
/// Typelist to hold cases for the switch
template<typename... Cases>
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<class CT, CT C, class T = std::integral_constant<CT, C>>
using SwitchCase = boost::hana::pair<std::integral_constant<CT, C>, T>;

namespace detail {

#define MORPHEUS_SWITCH_CASE(Z, N, _) \
case decltype(switchCondition(boost::mp11::mp_at_c<CaseList, N>{}))::value: \
return std::forward<CaseHandler>(caseHander)(decltype(switchTag(boost::mp11::mp_at_c<CaseList, N>{})){});

template<class Result, class Condition, class DefaultHandler>
Result invokeDefaultHandler(Condition condition, DefaultHandler defaultHandler)
{
if constexpr (!std::is_void_v<Result> && std::is_void_v<std::invoke_result_t<DefaultHandler, Condition>>)
{
defaultHandler(condition);
unreachable();
}
else
return defaultHandler(condition);
}

#define MORPHEUS_SWITCH_OVERLOAD(Z, N, _) \
template <class Result, class Condition, class CaseList, class CaseHandler, class DefaultCase> \
constexpr Result switch_(std::integral_constant<std::size_t, N>, Condition const condition, CaseList const cases, CaseHandler&& caseHander, \
DefaultCase&& defaultCase) \
{ \
switch (condition) { \
BOOST_PP_REPEAT_##Z(N, MORPHEUS_SWITCH_CASE, ~) \
} \
return invokeDefaultHandler<Result, Condition, DefaultCase>(condition, defaultCase); \
}

BOOST_PP_REPEAT(MORPHEUS_SWITCH_MAX, MORPHEUS_SWITCH_OVERLOAD, ~)
#undef MORPHEUS_SWITCH_CASE
#undef MORPHEUS_SWITCH_OVERLOAD

template<class... T> struct SwitchCommonReference {
using type = std::conditional_t<(std::is_rvalue_reference_v<T> && ...), std::add_rvalue_reference_t<std::common_type_t<T...>>,
std::conditional_t<(std::is_reference_v<T> && ...), std::add_lvalue_reference_t<std::common_type_t<T...>>,
std::common_type_t<T...>>>;
};

template<class Condition, class CaseList, class CaseHandler, class DefaultHandler>
auto switchResult(CaseList caseList)
{
constexpr std::size_t Size = boost::mp11::mp_size<CaseList>::value;
if constexpr (Size == 0u)
return std::invoke_result<DefaultHandler, Condition>{};
else
return [&]<std::size_t... I>(std::index_sequence<I...>)
{
return SwitchCommonReference<
std::invoke_result_t<CaseHandler, decltype(switchTag(boost::mp11::mp_at_c<CaseList, I>{}))>...>{};
}(std::make_index_sequence<Size>{});
}

template<class Condition, class CaseList, class CaseHandler, class DefaultHandler>
using SwitchResult = typename decltype(switchResult<Condition, CaseList, CaseHandler, DefaultHandler>(std::declval<CaseList>()))::type;

}

/// Compile time generation of switch statements. Maps a given list of values This is
///
template<class Condition, class CaseList, class CaseHandler, class DefaultCase = decltype(defaultCase)>
constexpr detail::SwitchResult<Condition, CaseList, CaseHandler, DefaultCase> 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<CaseList>::value;
static_assert(Size < MORPHEUS_SWITCH_MAX, "Increase MORPHEUS_SWITCH_MAX");
using Result = detail::SwitchResult<Condition, CaseList, CaseHandler, DefaultCase>;
return detail::switch_<Result, Condition, CaseList, CaseHandler, DefaultCase>
(std::integral_constant<std::size_t, Size>{}, condition, caseList,
std::forward<CaseHandler>(caseHander),
std::forward<DefaultCase>(defaultCase)
);
}

}
1 change: 1 addition & 0 deletions libraries/core/tests/functional/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
target_sources(MorpheusCoreTests
PRIVATE
function_ref.tests.cpp
switch.tests.cpp
)
112 changes: 112 additions & 0 deletions libraries/core/tests/functional/switch.tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#include "morpheus/core/functional/switch.hpp"

#include <boost/hana/size.hpp>
#include <boost/hana/transform.hpp>
#include <boost/hana/tuple.hpp>
#include <boost/hana/zip.hpp>
#include <boost/mp11.hpp>
#include <catch2/catch_all.hpp>
#include <magic_enum/magic_enum_format.hpp>

#include <tuple>

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<bool, char, int, float> 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<int, 0>, SwitchCase<int, 1>, SwitchCase<int, 2>, SwitchCase<int, 3>>;
STATIC_REQUIRE(boost::mp11::mp_size<Cases>::value == 4);
for (int i = 0; i < boost::mp11::mp_size<Cases>::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<bool, char, int, float>;
// auto caseValues = []<std::size_t... Is>(std::index_sequence<Is...>)
// {
// return boost::hana::tuple_c<std::size_t, Is...>;
// }(std::make_index_sequence<boost::hana::size(types)>());
// auto caseParams = boost::hana::zip(types, caseValues);
// auto cases = boost::hana::transform(caseParams,
// [](auto const params)
// {
// return SwitchCase<decltype(+params[1_c]), +params[1_c], decltype(params[0_c])>;
// });

// using CaseTypes = std::tuple<bool, char, int, float>;
// using CaseValues = boost::mp11::mp_iota_c<4>;
// using Case = boost::hana::transform
// using Cases = boost::mp11::mp_transform<

using Cases = CaseList<SwitchCase<int, 0, bool>, SwitchCase<int, 1, char>, SwitchCase<int, 2, int>, SwitchCase<int, 3, float>>;
STATIC_REQUIRE(boost::mp11::mp_size<Cases>::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<Case, Case::A>, SwitchCase<Case, Case::B>, SwitchCase<Case, Case::C>, SwitchCase<Case, Case::D>>;
STATIC_REQUIRE(boost::mp11::mp_size<Cases>::value == magic_enum::enum_count<Case>());
magic_enum::enum_for_each<Case>(
[](auto const caseToCall)
{
switch_(caseToCall, Cases{},
[&](auto const executedCase)
{
REQUIRE(caseToCall == executedCase);
});
});
}

} // namespace morpheus::functional
Loading