diff --git a/README.md b/README.md index 60a595f9..e9aa5fc6 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,7 @@ unlocked the potential of `constexpr` algorithms and concepts. This translates t - `remove_scope` - `unscoped_string_to_enum` - `for_each_n` + - `dispatch` - iterators and more! - ***Transparency***: Compiler implementation variability fully documented, verifiable and reportable (see 9 above) @@ -434,7 +435,7 @@ requires std::invocable ``` Call supplied invocable for _each_ enum value. Similar to `std::for_each` except the first parameter of your invocable must accept an enum value (passed by `for_each`). Optionally provide any additional parameters. Works with lambdas, member functions, functions etc. You can limit the number of calls to your -invokable by using the `for_each_n` version with the first parameter being the maximum number to call. The second version of `for_each` and `for_each_n` is intended to be used +invocable by using the `for_each_n` version with the first parameter being the maximum number to call. The second version of `for_each` and `for_each_n` is intended to be used when using a member function - the _second_ parameter passed by your call must be the `this` pointer of the object. If you wish to pass a `reference` parameter, you must wrap it in `std::ref`. @@ -499,8 +500,7 @@ _output_ 14 10 14 10 <== invoked with returned object 74 -``` -Example with pointer to member function with additional parameters: +```Example with pointer to member function with additional parameters: ```c++ struct foo { @@ -518,7 +518,106 @@ _output_ ```CSV 160 ``` -## o) `is_scoped` +## o) `dispatch` +```c++ +template +static constexpr bool tuple_comp(const std::tuple& pl, const std::tuple& pr); + +template // with not found value(nval) for return +requires std::invocable +[[maybe_unused]] static constexpr R dispatch(T ev, R nval, const std::array, I>& disp, Args&&... args); + +template // specialisation for member function with not found value(nval) for return +requires std::invocable +[[maybe_unused]] static constexpr R dispatch(T ev, R nval, const std::array, I>& disp, C *obj, Args&&... args); + +template // void func with not found call to last element +requires (std::invocable && I > 0) +static constexpr void dispatch(T ev, const std::array, I>& disp, Args&&... args); + +template // specialisation for void member function with not found call to last element +requires (std::invocable && I > 0) +static constexpr void dispatch(T ev, const std::array, I>& disp, C *obj, Args&&... args); +``` +With a given enum, search and call user supplied invocable. Use this method where complex event handling is required, allowing you to easily declare predefined invocable actions +for different enum values. + +Where invocable returns a value, return this value or a user supplied "not found" value. +Where invocable is void, call user supplied "not found" invocable. +The first parameter of your invocable must accept an enum value (passed by `dispatch`). +Optionally provide any additional parameters. Works with lambdas, member functions, functions etc. + +There are two versions of `dispatch` - the first takes an enum value, a 'not found' value, and a `std::array` of `std::tuple` of enum and invocable. +The second version takes an enum value, and a `std::array` of `std::tuple` of enum and invocable. The last element of the array is called if the enum is not found. +This version is intended for use with `void` return invocables. +The second version of each of the above is intended to be used when using a member function - the _second_ parameter passed by your call must be the `this` pointer of the object. +You can also use `std::bind` to bind the this pointer and any parameter placeholders when declaring your array. +If you wish to pass a `reference` parameter, you must wrap it in `std::ref`. + +If you wish to provide a `constexpr` array, you will need to use a simple function prototype, since `std::function` is not constexpr (see unit tests for examples). +> [!TIP] +> Your `std::array` of `std::tuple` should be sorted by enum. +> The `dispatch` method performs a binary search on the array. Complexity for a sorted array is at most $2log_2(N)+O(1)$ comparisons. +> If the array is _not_ sorted, complexity is linear. +```c++ +enum class directions { left, right, up, down, forward, backward, notfound=-1 }; +static constexpr auto prn([](directions ev) { std::cout << conjure_enum::enum_to_string(ev) << '\n'; }); +static constexpr auto tarr +{ + std::to_array> + ({ + { directions::left, prn }, + { directions::right, prn }, + { directions::up, prn }, + { directions::down, prn }, + { directions::backward, prn }, + { directions::notfound, [](directions ev) { std::cout << "not found: "; prn(ev); } }, // not found func + }) +}; +conjure_enum::dispatch(directions::right, tarr); +conjure_enum::dispatch(directions::forward, tarr); +std::cout << conjure_enum::enum_to_int(directions::notfound) << '\n'; +``` +_output_ +```CSV +directions::right +not found: directions::forward +-1 +``` +This example uses member functions: +```c++ +struct foo +{ + int process(component val, int aint) + { + return aint * static_cast(val); + } + int process1(component val, int aint) + { + return aint + static_cast(val); + } +}; +constexpr auto tarr1 +{ + std::to_array> + ({ + { component::scheme, &foo::process }, + { component::port, &foo::process }, + { component::fragment, &foo::process1 }, + }) +}; +foo bar; +for (auto val : { component::scheme, component::path, component::port, component::fragment }) + std::cout << conjure_enum::dispatch(val, -1, tarr1, &bar, 1000) << '\n'; +``` +_output_ +```CSV +0 +-1 +6000 +1015 +``` +## p) `is_scoped` ```c++ struct is_scoped : std::integral_constant>; }>{}; @@ -533,7 +632,7 @@ _output_ true false ``` -## p) `is_valid` +## q) `is_valid` ```c++ template static constexpr bool is_valid(); @@ -548,7 +647,7 @@ _output_ true false ``` -## q) `type_name` +## r) `type_name` ```c++ static constexpr std::string_view type_name(); ``` @@ -562,7 +661,7 @@ _output_ component component1 ``` -## r) `remove_scope` +## s) `remove_scope` ```c++ static constexpr std::string_view remove_scope(std::string_view what); ``` @@ -576,7 +675,7 @@ _output_ path path ``` -## s) `add_scope` +## t) `add_scope` ```c++ static constexpr std::string_view add_scope(std::string_view what); ``` @@ -590,7 +689,7 @@ _output_ component::path path ``` -## t) `has_scope` +## u) `has_scope` ```c++ static constexpr bool has_scope(std::string_view what); ``` @@ -606,7 +705,7 @@ true false false ``` -## u) `iterators` +## v) `iterators` ```c++ static constexpr auto cbegin(); static constexpr auto cend(); @@ -633,7 +732,7 @@ _output_ 8 numbers::eight 9 numbers::nine ``` -## v) `iterator_adaptor` +## w) `iterator_adaptor` ```c++ template struct iterator_adaptor; @@ -656,7 +755,7 @@ _output_ 8 9 ``` -## w) `front, back` +## x) `front, back` ```c++ static constexpr auto front(); static constexpr auto back(); @@ -673,7 +772,7 @@ _output_ 0 numbers::zero 9 numbers::nine ``` -## x) `ostream_enum_operator` +## y) `ostream_enum_operator` ```c++ template, valid_enum T> constexpr std::basic_ostream& operator<<(std::basic_ostream& os, T value); @@ -695,7 +794,7 @@ _output_ "host" "100" ``` -## y) `epeek, tpeek` +## z) `epeek, tpeek` ```c++ static consteval const char *tpeek(); template @@ -937,7 +1036,7 @@ requires std::invocable ``` Call supplied invocable for _each bit that is on_. Similar to `std::for_each` except first parameter of your invocable must accept an enum value (passed by `for_each`). Optionally provide any additional parameters. Works with lambdas, member functions, functions etc. You can limit the number of calls to your -invokable by using the `for_each_n` version with the first parameter being the maximum number to call. The second version of `for_each` and `for_each_n` is intended to be used +invocable by using the `for_each_n` version with the first parameter being the maximum number to call. The second version of `for_each` and `for_each_n` is intended to be used when using a member function - the _second_ parameter passed by your call must be the `this` pointer of the object. If you wish to pass a `reference` parameter, you must wrap it in `std::ref`. diff --git a/include/fix8/conjure_enum.hpp b/include/fix8/conjure_enum.hpp index d21a070b..8177ee17 100644 --- a/include/fix8/conjure_enum.hpp +++ b/include/fix8/conjure_enum.hpp @@ -479,6 +479,46 @@ class conjure_enum : public static_only return for_each_n(n, std::bind(std::forward(func), obj, std::placeholders::_1, std::forward(args)...)); } + // dispatch + template + static constexpr bool tuple_comp(const std::tuple& pl, const std::tuple& pr) noexcept + { + return static_cast(std::get(pl)) < static_cast(std::get(pr)); + } + + template // with not found value(nval) for return + requires std::invocable + [[maybe_unused]] static constexpr R dispatch(T ev, R nval, const std::array, I>& disp, Args&&... args) noexcept + { + const auto result { std::equal_range(disp.cbegin(), disp.cend(), std::make_tuple(ev, Fn()), tuple_comp) }; + return result.first != result.second ? std::invoke(std::get(*result.first), ev, std::forward(args)...) : nval; + } + + template // specialisation for member function with not found value(nval) for return + requires std::invocable + [[maybe_unused]] static constexpr R dispatch(T ev, R nval, const std::array, I>& disp, C *obj, Args&&... args) noexcept + { + const auto result { std::equal_range(disp.cbegin(), disp.cend(), std::make_tuple(ev, Fn()), tuple_comp) }; + return result.first != result.second ? std::invoke(std::get(*result.first), obj, ev, std::forward(args)...) : nval; + } + + template // void func with not found call to last element + requires (std::invocable && I > 0) + static constexpr void dispatch(T ev, const std::array, I>& disp, Args&&... args) noexcept + { + const auto result { std::equal_range(disp.cbegin(), std::prev(disp.cend()), std::make_tuple(ev, Fn()), tuple_comp) }; + return result.first != result.second ? std::invoke(std::get(*result.first), ev, std::forward(args)...) + : std::invoke(std::get(*std::prev(disp.cend())), ev, std::forward(args)...); + } + + template // specialisation for void member function with not found call to last element + requires (std::invocable && I > 0) + static constexpr void dispatch(T ev, const std::array, I>& disp, C *obj, Args&&... args) noexcept + { + const auto result { std::equal_range(disp.cbegin(), std::prev(disp.cend()), std::make_tuple(ev, Fn()), tuple_comp) }; + return result.first != result.second ? std::invoke(std::get(*result.first), obj, ev, std::forward(args)...) + : std::invoke(std::get(*std::prev(disp.cend())), obj, ev, std::forward(args)...); + } // public constexpr data structures static constexpr auto values { _values() }; static constexpr auto entries { _entries(std::make_index_sequence()) }; diff --git a/reference/source_location.md b/reference/source_location.md index 34eb6ff9..371a68ae 100644 --- a/reference/source_location.md +++ b/reference/source_location.md @@ -457,7 +457,7 @@ static consteval const char* conjure_type::tpeek() [with T = TEST::TEST1::Nin ``` --- -# Compiler: Clang: Ubuntu Clang 18.1.3 (1) +# Compiler: Clang: Ubuntu Clang 18.1.3 (1ubuntu1) ## 1. scoped enum ```c++ static const char *conjure_type::tpeek() [T = Namespace_Enum_Type] diff --git a/utests/unittests.cpp b/utests/unittests.cpp index 6c3804c8..43e15425 100644 --- a/utests/unittests.cpp +++ b/utests/unittests.cpp @@ -44,6 +44,7 @@ using namespace std::literals::string_literals; enum class component : int { scheme, authority, userinfo, user, password, host, port, path=12, test=path, query, fragment }; enum component1 : int { scheme, authority, userinfo, user, password, host, port, path=12, query, fragment }; enum class numbers : int { zero, one, two, three, four, five, FIVE=five, six, seven, eight, nine }; +enum class directions { left, right, up, down, forward, backward, notfound=-1 }; //----------------------------------------------------------------------------------------- // run as: ctest --output-on-failure @@ -353,6 +354,156 @@ TEST_CASE("for_each_n") REQUIRE(total == 33); } +//----------------------------------------------------------------------------------------- +TEST_CASE("dispatch") +{ + const auto dd1 + { + std::to_array>> + ({ + { component::scheme, [](component ev, int a) { return a * 100 + conjure_enum::enum_to_int(ev); } }, + { component::port, [](component ev, int a) { return a * 200 + conjure_enum::enum_to_int(ev); } }, + { component::fragment, [](component ev, int a) { return a * 300 + conjure_enum::enum_to_int(ev); } }, + }) + }; + REQUIRE(conjure_enum::dispatch(component::port, -1, dd1, 10) == 2006); + + struct foo + { + int process(component val, int aint) + { + return aint * static_cast(val); + } + }; + foo bar; + const auto dd2 + { + std::to_array>> + ({ + { component::scheme, std::bind(&foo::process, &bar, std::placeholders::_1, std::placeholders::_2) }, + { component::port, std::bind(&foo::process, &bar, std::placeholders::_1, std::placeholders::_2) }, + { component::fragment, std::bind(&foo::process, &bar, std::placeholders::_1, std::placeholders::_2) }, + }) + }; + REQUIRE(conjure_enum::dispatch(component::port, -1, dd2, 1000) == 6000); + const auto dd2a + { + std::to_array> + ({ + { component::scheme, &foo::process }, + { component::port, &foo::process }, + { component::fragment, &foo::process }, + }) + }; + REQUIRE(conjure_enum::dispatch(component::port, -1, dd2a, &bar, 1000) == 6000); + + const auto dd3 + { + std::to_array>> + ({ + { component::scheme, [](component ev, int& a) { a += 1000 + conjure_enum::enum_to_int(ev); } }, + { component::port, [](component ev, int& a) { a += 2000 + conjure_enum::enum_to_int(ev); } }, + { component::fragment, [](component ev, int& a) { a += 3000 + conjure_enum::enum_to_int(ev); } }, + { static_cast(-1), []([[maybe_unused]] component ev, int& a) { a = -1; } }, // not found func + }) + }; + int total1{}; + conjure_enum::dispatch(component::port, dd3, std::ref(total1)); + REQUIRE(total1 == 2006); + conjure_enum::dispatch(component::path, dd3, std::ref(total1)); + REQUIRE(total1 == -1); + + // test empty + const std::array>, 0> dd4; + REQUIRE(conjure_enum::dispatch(component::path, -1, dd4) == -1); + + const std::array>, 1> dd5 + {{{ static_cast(-1), []([[maybe_unused]] component ev, int& a) { a = -1; }}}}; + total1 = 0; + conjure_enum::dispatch(component::path, dd5, std::ref(total1)); + REQUIRE(total1 == -1); +} + +//----------------------------------------------------------------------------------------- +TEST_CASE("constexpr dispatch") +{ + constexpr auto dd1 + { + std::to_array> + ({ + { component::scheme, [](component ev, int a) { return a * 1000 + conjure_enum::enum_to_int(ev); } }, + { component::port, [](component ev, int a) { return a * 2000 + conjure_enum::enum_to_int(ev); } }, + { component::fragment, [](component ev, int a) { return a * 3000 + conjure_enum::enum_to_int(ev); } }, + }) + }; + REQUIRE(conjure_enum::dispatch(component::port, -1, dd1, 1000) == 2000006); + REQUIRE(conjure_enum::dispatch(component::path, -1, dd1, 1000) == -1); + + constexpr auto dd2 + { + std::to_array> + ({ + { component::scheme, [](component ev, int& a) { a += 1000 + conjure_enum::enum_to_int(ev); } }, + { component::port, [](component ev, int& a) { a += 2000 + conjure_enum::enum_to_int(ev); } }, + { component::fragment, [](component ev, int& a) { a += 3000 + conjure_enum::enum_to_int(ev); } }, + { static_cast(-1), []([[maybe_unused]] component ev, int& a) { a = -1; } }, // not found func + }) + }; + int total{}; + conjure_enum::dispatch(component::port, dd2, std::ref(total)); + REQUIRE(total == 2006); + conjure_enum::dispatch(component::path, dd2, std::ref(total)); + REQUIRE(total == -1); + + struct foo + { + int process(component val, int aint) + { + return aint * static_cast(val); + } + }; + foo bar; + constexpr auto dd2a + { + std::to_array> + ({ + { component::scheme, &foo::process }, + { component::port, &foo::process }, + { component::fragment, &foo::process }, + }) + }; + REQUIRE(conjure_enum::dispatch(component::port, -1, dd2a, &bar, 1000) == 6000); + + // test empty + constexpr std::array, 0> dd4; + REQUIRE(conjure_enum::dispatch(component::path, -1, dd4) == -1); + + constexpr std::array, 1> dd5 + {{{ static_cast(-1), []([[maybe_unused]] component ev, int& a) { a = -1; }}}}; + int total1{}; + conjure_enum::dispatch(component::path, dd5, std::ref(total1)); + REQUIRE(total1 == -1); + + static constexpr auto prn([](directions ev, int& a) { a = conjure_enum::enum_to_int(ev); }); + static constexpr auto tarr + { + std::to_array> + ({ + { directions::left, prn }, + { directions::right, prn }, + { directions::up, prn }, + { directions::down, prn }, + { directions::backward, prn }, + { directions::notfound, []([[maybe_unused]] directions ev, int& a) { a = -1; } }, // not found func + }) + }; + int val; + conjure_enum::dispatch(directions::right, tarr, std::ref(val)); + REQUIRE(val == 1); + conjure_enum::dispatch(directions::forward, tarr, std::ref(val)); + REQUIRE(val == -1); +} + //----------------------------------------------------------------------------------------- TEST_CASE("enum_bitset") {