diff --git a/README.md b/README.md index 5670807..168a97a 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,8 @@ Header-only C++17 library provides static reflection for enums, work with any en * `enum_contains` checks whether enum contains enumerator with such value. * `enum_type_name` returns name of enum type. * `enum_fuse` allows multidimensional switch/cases. +* `enum_switch` allows runtime enum value transformation to constexpr context. +* `enum_for_each` calls a function with all enum constexpr value. * `is_unscoped_enum` checks whether type is an [Unscoped enumeration](https://en.cppreference.com/w/cpp/language/enum#Unscoped_enumeration). * `is_scoped_enum` checks whether type is an [Scoped enumeration](https://en.cppreference.com/w/cpp/language/enum#Scoped_enumerations). * `underlying_type` improved UB-free "SFINAE-friendly" [underlying_type](https://en.cppreference.com/w/cpp/types/underlying_type). @@ -149,6 +151,24 @@ enum class Color { RED = 2, BLUE = 4, GREEN = 8 }; // ... } ``` + +* Enum switch runtime value as constexpr constant + ```cpp + Color color = Color::RED; + + magic_enum::enum_switch([] (auto val) { + constexpr Color c_color = val; + // ... + }, color); + ``` + +* Enum iterate for each enum as constexpr constant + ```cpp + magic_enum::enum_for_each([] (auto val) { + constexpr Color c_color = val; + // ... + }); + ``` * Ostream operator for enum diff --git a/doc/reference.md b/doc/reference.md index 7bb0f50..665e7a7 100644 --- a/doc/reference.md +++ b/doc/reference.md @@ -13,6 +13,8 @@ * [`enum_contains` checks whether enum contains enumerator with such value.](#enum_contains) * [`enum_type_name` returns type name of enum.](#enum_type_name) * [`enum_fuse` returns a bijective mix of enum values.](#enum_fuse) +* [`enum_switch` TODO.](#enum_switch) +* [`enum_for_each` TODO.](#enum_for_each) * [`is_unscoped_enum` checks whether type is an Unscoped enumeration.](#is_unscoped_enum) * [`is_scoped_enum` checks whether type is an Scoped enumeration.](#is_scoped_enum) * [`underlying_type` improved UB-free "SFINAE-friendly" underlying_type.](#underlying_type) @@ -364,6 +366,14 @@ template } ``` +## `enum_switch` + +TODO + +## `enum_for_each` + +TODO + ## `is_unscoped_enum` ```cpp diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index b6e47a7..c4dd793 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -20,6 +20,7 @@ endfunction() make_example(example) make_example(enum_flag_example) make_example(example_custom_name) +make_example(example_switch) if(MAGIC_ENUM_OPT_ENABLE_NONASCII) if(((CMAKE_CXX_COMPILER_ID MATCHES "GNU") AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 10.0) OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang")) set(OPTIONS ${OPTIONS} -DMAGIC_ENUM_ENABLE_NONASCII) diff --git a/example/example_switch.cpp b/example/example_switch.cpp new file mode 100644 index 0000000..4884805 --- /dev/null +++ b/example/example_switch.cpp @@ -0,0 +1,112 @@ +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019 - 2021 Daniil Goncharov . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include + +#include + +enum class Color { RED, BLUE, GREEN }; + +template +constexpr std::string_view DoWork() { + return "default"; +} + +template <> +constexpr std::string_view DoWork() { + return "override"; +} + +// helper type for the visitor pattern +template struct overloaded : Ts... { using Ts::operator()...; }; +template overloaded(Ts...) -> overloaded; + + +int main() { + Color c = Color::RED; + + auto lambda = [] (auto value) { + std::cout << DoWork() << std::endl; + }; + + magic_enum::enum_switch(lambda, c); // prints "default" + + c = Color::GREEN; + + magic_enum::enum_switch(lambda, c); // prints "override" + + + // with object, explicit enum type + auto switcher1 = overloaded { + [] (auto val) -> std::enable_if_t <(Color::BLUE == decltype(val){}())> { + std::cout << "Blue" << std::endl; + }, + [] (std::integral_constant) { + std::cout << "Red" << std::endl; + } + }; + + magic_enum::enum_switch(switcher1, 2 /* GREEN */); // prints nothing + magic_enum::enum_switch(switcher1, 1 /* BLUE */); // prints "Blue" + magic_enum::enum_switch(switcher1, 0 /* RED */); // prints "Red" + + + // explicit result type + auto switcher2 = overloaded { + [] (std::integral_constant) { + return "called with green argument"; + }, + [] (Color other) { // default case + auto name = magic_enum::enum_name(other); // not empty + return "default: " + std::string{name.data(), name.size()}; + } + }; + + std::cout << magic_enum::enum_switch(switcher2, Color::GREEN) << std::endl; // prints "called with green argument" + std::cout << magic_enum::enum_switch(switcher2, Color::RED) << std::endl; // prints "default: RED" + + auto empty = magic_enum::enum_switch(switcher2, static_cast(-3)); // returns an empty string + assert(empty.empty()); + + + // result with default object + std::cout << magic_enum::enum_switch(switcher2, -3, "unrecognized") << std::endl; // prints "unrecognized" + + auto switcher3 = overloaded { + [] (std::integral_constant) { + return "red result"; + }, + [] (std::integral_constant) { + return std::nullopt; + } + }; + + std::cout << std::boolalpha; + std::cout << magic_enum::enum_switch(switcher3, "GREEN", std::make_optional("cica")).value() << std::endl; // prints default: "cica" + std::cout << magic_enum::enum_switch(switcher3, "RED", std::make_optional("cica")).value() << std::endl; // prints: "red result" + std::cout << magic_enum::enum_switch(switcher3, "BLUE", std::make_optional("cica")).has_value() << std::endl; // prints: false + std::cout << magic_enum::enum_switch(switcher3, "red", std::make_optional("cica"), magic_enum::case_insensitive).value() << std::endl; // prints: "red result" + + std::cout << magic_enum::enum_switch(switcher3, Color::GREEN, std::make_optional("cica")).value() << std::endl; // prints default: "cica" + std::cout << magic_enum::enum_switch(switcher3, Color::RED, std::make_optional("cica")).value() << std::endl; // prints: "red result" + std::cout << magic_enum::enum_switch(switcher3, Color::BLUE, std::make_optional("cica")).has_value() << std::endl; // prints: false +} \ No newline at end of file diff --git a/include/magic_enum.hpp b/include/magic_enum.hpp index a17e30f..4856164 100644 --- a/include/magic_enum.hpp +++ b/include/magic_enum.hpp @@ -259,7 +259,7 @@ class case_insensitive { public: template - constexpr auto operator()([[maybe_unused]] L lhs, [[maybe_unused]] R rhs) noexcept -> std::enable_if_t, char> && std::is_same_v, char>, bool> { + constexpr auto operator()([[maybe_unused]] L lhs, [[maybe_unused]] R rhs) const noexcept -> std::enable_if_t, char> && std::is_same_v, char>, bool> { #if defined(MAGIC_ENUM_ENABLE_NONASCII) static_assert(always_false_v, "magic_enum::case_insensitive not supported Non-ASCII feature."); return false; @@ -807,16 +807,26 @@ constexpr bool no_duplicate() { T(192)T(193)T(194)T(195)T(196)T(197)T(198)T(199)T(200)T(201)T(202)T(203)T(204)T(205)T(206)T(207)T(208)T(209)T(210)T(211)T(212)T(213)T(214)T(215)T(216)T(217)T(218)T(219)T(220)T(221)T(222)T(223) \ T(224)T(225)T(226)T(227)T(228)T(229)T(230)T(231)T(232)T(233)T(234)T(235)T(236)T(237)T(238)T(239)T(240)T(241)T(242)T(243)T(244)T(245)T(246)T(247)T(248)T(249)T(250)T(251)T(252)T(253)T(254)T(255) -#define MAGIC_ENUM_CASE(val) \ - case cases[val]: \ - if constexpr ((val) + page < size) { \ - if (!pred(values[val + page], searched)) \ - break; \ - if constexpr (call_v == case_call_t::index && std::is_invocable_r_v>) \ - return detail::invoke_r(std::forward(lambda), std::integral_constant{}); \ - else if constexpr (call_v == case_call_t::value && std::is_invocable_r_v>) \ - return detail::invoke_r(std::forward(lambda), std::integral_constant{}); \ - break; \ +#define MAGIC_ENUM_CASE(val) \ + case cases[val]: \ + if constexpr ((val) + page < size) { \ + if (!pred(values[val + page], searched)) { \ + break; \ + } \ + if constexpr (call_v == case_call_t::index) { \ + if constexpr (std::is_invocable_r_v>) { \ + return detail::invoke_r(std::forward(lambda), std::integral_constant{}); \ + } else if constexpr (std::is_invocable_v>) { \ + assert(false && "wrong result type"); \ + } \ + } else if constexpr (call_v == case_call_t::value) { \ + if constexpr (std::is_invocable_r_v>) { \ + return detail::invoke_r(std::forward(lambda), std::integral_constant{}); \ + } else if constexpr (std::is_invocable_r_v>) { \ + assert(false && "wrong result type"); \ + } \ + } \ + break; \ } else [[fallthrough]]; template constexpr_switch( #undef MAGIC_ENUM_FOR_EACH_256 #undef MAGIC_ENUM_CASE +template +constexpr auto enum_for_each_impl(Lambda&& lambda, std::index_sequence) { + constexpr bool has_void_return = (std::is_void_v[Ix]>>> || ...); + constexpr bool all_same_return = (std::is_same_v< + std::invoke_result_t[0]>>, + std::invoke_result_t[Ix]>>> && ...); + + if constexpr (has_void_return) { + (lambda(std::integral_constant[Ix]>{}), ...); + } else if constexpr (all_same_return) { + return std::array{lambda(std::integral_constant[Ix]>{}) ...}; + } else { + return std::tuple{lambda(std::integral_constant[Ix]>{}) ...}; + } +} + } // namespace magic_enum::detail // Checks is magic_enum supported compiler. @@ -1151,6 +1177,59 @@ template > return static_cast(enum_cast>(value, std::move_if_noexcept(p))); } +template +constexpr auto enum_switch(Lambda&& lambda, E value) -> detail::enable_if_enum_t { + return detail::constexpr_switch<&detail::values_v>, detail::case_call_t::value>( + std::forward(lambda), value, detail::default_result_type_lambda); +} + +template +constexpr auto enum_switch(Lambda&& lambda, E value, ResultType&& result) -> detail::enable_if_enum_t { + return detail::constexpr_switch<&detail::values_v>, detail::case_call_t::value>( + std::forward(lambda), value, + [&result] { return std::forward(result); }); +} + +template, typename Lambda> +constexpr auto enum_switch(Lambda&& lambda, std::string_view name, BinaryPredicate&& p = {}) + -> std::enable_if_t> && std::is_invocable_r_v, ResultType> { + if (auto value = enum_cast(name, std::forward(p))) { + return enum_switch(std::forward(lambda), *value); + } + return detail::default_result_type_lambda(); +} + +template, typename Lambda> +constexpr auto enum_switch(Lambda&& lambda, std::string_view name, ResultType&& result, BinaryPredicate&& p = {}) + -> std::enable_if_t> && std::is_invocable_r_v, ResultType> { + if (auto value = enum_cast(name, std::forward(p))) { + return enum_switch(std::forward(lambda), *value, std::forward(result)); + } + return std::forward(result); +} + +template +constexpr auto enum_switch(Lambda&& lambda, underlying_type_t> raw_value) -> detail::enable_if_enum_t { + if (auto value = enum_cast(raw_value)) { + return enum_switch(std::forward(lambda), *value); + } + return detail::default_result_type_lambda(); +} + +template +constexpr auto enum_switch(Lambda&& lambda, underlying_type_t> raw_value, ResultType&& result) -> detail::enable_if_enum_t { + if (auto value = enum_cast(raw_value)) { + return enum_switch(std::forward(lambda), *value, std::forward(result)); + } + return std::forward(result); +} + +template +constexpr auto enum_for_each(Lambda&& lambda) + -> detail::enable_if_enum_t(std::declval(), std::make_index_sequence>{}))> { + return detail::enum_for_each_impl(std::forward(lambda), std::make_index_sequence>{}); +} + namespace detail { template diff --git a/test/test.cpp b/test/test.cpp index 1b9f219..34ae993 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -1018,6 +1018,61 @@ TEST_CASE("cmp_less") { } } +template +constexpr std::string_view DoWork() { + return "default"; +} + +template<> +constexpr std::string_view DoWork() { + return "override"; +} + +TEST_CASE("enum_switch") { + constexpr auto bind_enum_switch = [] (Color c) { + + return enum_switch([](auto val) { + return DoWork(); + }, c, string_view{"unrecognized"}); + + }; + + constexpr auto def = bind_enum_switch(Color::BLUE); + REQUIRE(def == "default"); + REQUIRE(bind_enum_switch(Color::RED) == "default"); + REQUIRE(bind_enum_switch(Color::GREEN) == "override"); + REQUIRE(bind_enum_switch(static_cast(0)) == "unrecognized"); +} + +TEST_CASE("enum_for_each") { + SECTION("no return type") { + underlying_type_t sum{}; + enum_for_each([&sum](auto val) { + constexpr underlying_type_t v = enum_integer(val()); + sum += v; + }); + REQUIRE(sum == 10); + } + + SECTION("same return type") { + constexpr auto workResults = enum_for_each([](auto val) { + return DoWork(); + }); + REQUIRE(workResults == std::array{"default", "override", "default"}); + } + + SECTION("different return type") { + constexpr auto colorInts = enum_for_each([](auto val) { + return val; + }); + REQUIRE(std::is_same_v, std::tuple< + std::integral_constant, + std::integral_constant, + std::integral_constant + >>); + } +} + #if defined(__clang__) && __clang_major__ >= 5 || defined(__GNUC__) && __GNUC__ >= 9 || defined(_MSC_VER) && _MSC_VER >= 1920 # define MAGIC_ENUM_SUPPORTED_CONSTEXPR_FOR 1 #endif