Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compile type switch statement #175

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -2,4 +2,5 @@ target_sources(MorpheusCore
PUBLIC
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 unhandledValue){};

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_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