diff --git a/include/asenum/asenum.h b/include/asenum/asenum.h index 84ff5d2..18fbc54 100644 --- a/include/asenum/asenum.h +++ b/include/asenum/asenum.h @@ -26,94 +26,157 @@ #include +/// Declares Associated Enum with 'name' and associates it with 'enum' +#define ASENUM_DECLARE(name, enum) ASENUM_DECLARE_IMPL(name, enum) + +/// Defines Associated Enum internal stuff. Must be placed first line inside 'ASENUM_DECLARE' (see example below) +#define ASENUM_DEFINE_STRUCTORS() ASENUM_DEFINE_STRUCTORS_IMPL() + +/// Defines association between enum 'case' and 'type' bound to this case +#define ASENUM_CASE(case, type) ASENUM_CASE_IMPL(case, type) + +/// Defines association between enum 'case' and void type bound to this case +#define ASENUM_CASE_VOID(case) ASENUM_CASE_VOID_IMPL(case) + + /** - // ===== DECLARATION ===== - enum class Setting + enum class ErrorCode { - Host, - Port, + Unknown, + Success, Timeout }; - // Declare associated enum 'AnySetting' with style of CamelCase without 'get' word in getters. - ASENUM_DECLARE(AnySetting, Setting) + // Associate enum 'ErrorCode' with AsEnum 'AnyError' + ASENUM_DECLARE(AnyError, ErrorCode) { ASENUM_DEFINE_STRUCTORS(); - - ASENUM_CASE_CC(Host, std::string); - ASENUM_CASE_CC(Port, uint16_t); - ASENUM_CASE_CC(Timeout, std::chrono::seconds); - }; + ASENUM_CASE(Unknown, std::string); + ASENUM_CASE_VOID(Success); + ASENUM_CASE(Timeout, std::chrono::seconds); + }; //===== USAGE ===== - void LogSetting(const AnySetting& setting) + void LogSetting(const AnyError& event) { - switch (setting.type()) + event.doSwitch() + .asCase([] (const std::string& value) { + std::cout << "Unknown error: " << value << "\n"; + }) + .asCase([] { + std::cout << "Success\n"; + }) + .asCase([] (const std::chrono::seconds& value) { + std::cout << "Timed out after: " << value.count() << "\n"; + }) + .asDefault([] { + std::cout << "Default\n"; + }); + + // === vs === + + switch (event.type()) + { + case ErrorCode::Unknown: + std::cout << "Unknown error: " << event.asUnknown() << "\n"; + break; + case ErrorCode::Success: + std::cout << "Success\n"; + break; + case ErrorCode::Timeout: + std::cout << "Timed out after: " << event.asTimeout().count() << "\n"; + break; + default: + std::cout << "Default\n"; + break; + } + + // === vs === + + if (event.is()) + { + std::cout << "Unknown error: " << event.asUnknown() << "\n"; + } + else if (event.is()) { - case Setting::Host: - std::cout << "Host: " << setting.Host() << "\n"; - break; - case Setting::Port: - std::cout << "Port: " << setting.Port()<< "\n"; - break; - case Setting::Timeout: - std::cout << "Timeout: " << setting.Timeout().count() << "\n"; - break; - default: - break; + std::cout << "Success\n"; + } + else if (event.is()) + { + std::cout << "Timed out after: " << event.asTimeout().count() << "\n"; } } + int main() + { + // ===== CREATION ===== + LogSetting(AnyError::createUnknown("test.api.com")); + LogSetting(AnyError::createSuccess()); + LogSetting(AnyError::createTimeout(std::chrono::seconds(1))); + + // === vs === + + LogSetting(AnyError::create("test.api.com")); + LogSetting(AnyError::create()); + LogSetting(AnyError::create(std::chrono::seconds(1))); - // ===== CREATION ===== - LogSetting(AnySetting::CreateHost("test.api.com")); - LogSetting(AnySetting::CreatePort(65535)); - LogSetting(AnySetting::CreateTimeout(std::chrono::seconds(1)); + return 0; + } */ -#define ASENUM_DECLARE(name, enum) \ + +// Private implementation details + + +#define ASENUM_DECLARE_IMPL(name, enum) \ class name: protected asenum::impl::AsEnum -#define ASENUM_DEFINE_STRUCTORS() \ +#define ASENUM_DEFINE_STRUCTORS_IMPL() \ private: \ using AsEnum::AsEnum; \ \ template \ struct AssociatedType { using Type = T; }; \ \ - template \ + template \ struct CaseCast; \ \ public: \ - template \ - const typename CaseCast::Type& as() const \ + template \ + static ThisType create(typename CaseCast::Type value) \ { \ - return validatedValueOfType::Type>(t_type); \ + return ThisType(T_type, std::move(value)); \ } \ \ - using AsEnum::type - - -/// For enums named in CamelCase style -#define ASENUM_CASE_CC(case, type) ASENUM_CASE_IMPL(case, type, Create) - -/// For enums named in lowerCamelCase style -#define ASENUM_CASE_LCC(case, type) ASENUM_CASE_IMPL(case, type, create) - -/// For enums named in snake_case style -#define ASENUM_CASE_SC(case, type) ASENUM_CASE_IMPL(case, type, create_) - + template \ + static ThisType create() \ + { \ + return ThisType(T_type); \ + } \ + \ + template \ + const typename CaseCast::Type& as() const \ + { \ + return validatedValueOfType::Type>(T_type); \ + } \ + \ + asenum::impl::AsSwitch doSwitch() const \ + { \ + return asenum::impl::AsSwitch(*this); \ + } \ + \ + using AsEnum::type; \ + using AsEnum::is -// Private stuff -#define ASENUM_CASE_IMPL(case, type, createToken) \ +#define ASENUM_CASE_IMPL(case, type) \ public: \ - static ThisType createToken##case(type value) \ + static ThisType create##case(type value) \ { \ - return ThisType(AssociatedEnum::case, std::move(value)); \ + return create(std::move(value)); \ } \ \ const type& as##case() const \ @@ -121,10 +184,31 @@ public: \ return validatedValueOfType(AssociatedEnum::case); \ } \ \ + bool is##case() const \ + { \ + return is(); \ + } \ + \ template <> \ struct CaseCast : AssociatedType {} +#define ASENUM_CASE_VOID_IMPL(case) \ +public: \ + static ThisType create##case() \ + { \ + return create(); \ + } \ + \ + bool is##case() const \ + { \ + return is(); \ + } \ + \ + template <> \ + struct CaseCast : AssociatedType {} + + namespace asenum { namespace impl @@ -136,13 +220,8 @@ namespace asenum using AssociatedEnum = Enum; using ThisType = ConcreteType; - Enum type() const - { - return m_type; - } - template - AsEnum(const Enum type, T&& value) + AsEnum(const AssociatedEnum type, T&& value) : m_type(type) , m_value(new T(std::forward(value)), [] (void* ptr) { if (ptr) @@ -152,20 +231,106 @@ namespace asenum }) {} + AsEnum(const AssociatedEnum type) + : m_type(type) + {} + + AssociatedEnum type() const + { + return m_type; + } + + template + bool is() const + { + return type() == T_type; + } + template - const T& validatedValueOfType(const Enum type) const + const T& validatedValueOfType(const AssociatedEnum type) const { if (m_type != type) { throw std::invalid_argument("Trying to get value of invalid type."); } + if (!m_value) + { + throw std::logic_error("Trying to get value of void associated type."); + } + return *reinterpret_cast(m_value.get()); } private: - const Enum m_type; + const AssociatedEnum m_type; const std::shared_ptr m_value; }; + + template class CaseCast, Enum... types> + class AsSwitch + { + template struct Contains; + template struct Contains { static const bool value = false; }; + template struct Contains { static const bool value = T_type1 == T_type2; }; + + template + struct Contains { static const bool value = Contains::value || Contains::value; }; + + template + struct HandlerCaller; + + template + struct HandlerCaller + { + static void call(const ConcreteType&, const Handler& handler) + { + handler(); + } + }; + + template + struct HandlerCaller + { + static void call(const ConcreteType& asEnum, const Handler& handler) + { + handler(asEnum.template as()); + } + }; + + public: + AsSwitch(const ConcreteType& asEnum, const bool handled) : m_asEnum(asEnum), m_handled(handled) {} + explicit AsSwitch(const ConcreteType& asEnum) : AsSwitch(asEnum, false) {} + + template + AsSwitch asCase(const CaseHandler& handler) + { + static_assert(!Contains::value, "Duplicated switch case"); + + if (!m_handled && T_type == m_asEnum.type()) + { + m_handled = true; + + constexpr bool isVoid = std::is_same::Type, void>::value; + HandlerCaller::call(m_asEnum, handler); + } + + return AsSwitch(m_asEnum, m_handled); + } + + template + void asDefault(const Handler& handler) + { + if (!m_handled) + { + m_handled = true; + handler(); + } + } + + private: + const ConcreteType& m_asEnum; + bool m_handled; + }; } } diff --git a/tests/AsEnumTest.cpp b/tests/AsEnumTest.cpp index 67d1762..1f979b4 100644 --- a/tests/AsEnumTest.cpp +++ b/tests/AsEnumTest.cpp @@ -42,18 +42,18 @@ namespace ASENUM_DECLARE(TestAsEnum, TestEnum) { ASENUM_DEFINE_STRUCTORS(); - - ASENUM_CASE_CC(Unknown, int); - ASENUM_CASE_CC(StringOpt, std::string); - ASENUM_CASE_CC(BoolOpt, bool); + + ASENUM_CASE(Unknown, int); + ASENUM_CASE(StringOpt, std::string); + ASENUM_CASE(BoolOpt, bool); }; } -TEST(AssEnum, NamedGetter) +TEST(AsEnum, NamedGetter) { - const TestAsEnum value1 = TestAsEnum::CreateStringOpt("test"); - const TestAsEnum value2 = TestAsEnum::CreateBoolOpt(true); - const TestAsEnum value3 = TestAsEnum::CreateUnknown(-100500); + const TestAsEnum value1 = TestAsEnum::createStringOpt("test"); + const TestAsEnum value2 = TestAsEnum::createBoolOpt(true); + const TestAsEnum value3 = TestAsEnum::createUnknown(-100500); EXPECT_EQ(value1.type(), TestEnum::StringOpt); EXPECT_EQ(value2.type(), TestEnum::BoolOpt); @@ -73,26 +73,88 @@ TEST(AssEnum, NamedGetter) } -TEST(AssEnum, EnumGetter) +TEST(AsEnum, EnumGetter) { - const TestAsEnum value1 = TestAsEnum::CreateStringOpt("test"); - const TestAsEnum value2 = TestAsEnum::CreateBoolOpt(true); - const TestAsEnum value3 = TestAsEnum::CreateUnknown(-100500); - - EXPECT_EQ(value1.type(), TestEnum::StringOpt); - EXPECT_EQ(value2.type(), TestEnum::BoolOpt); - EXPECT_EQ(value3.type(), TestEnum::Unknown); - - EXPECT_EQ(value1.as(), "test"); - EXPECT_THROW(value1.as(), std::exception); - EXPECT_THROW(value1.as(), std::exception); - - EXPECT_EQ(value2.as(), true); - EXPECT_THROW(value2.as(), std::exception); - EXPECT_THROW(value2.as(), std::exception); - - EXPECT_EQ(value3.as(), -100500); - EXPECT_THROW(value3.as(), std::exception); - EXPECT_THROW(value3.as(), std::exception); - + const TestAsEnum value1 = TestAsEnum::create("test"); + const TestAsEnum value2 = TestAsEnum::create(true); + const TestAsEnum value3 = TestAsEnum::create(-100500); + + EXPECT_EQ(value1.type(), TestEnum::StringOpt); + EXPECT_EQ(value2.type(), TestEnum::BoolOpt); + EXPECT_EQ(value3.type(), TestEnum::Unknown); + + EXPECT_EQ(value1.as(), "test"); + EXPECT_THROW(value1.as(), std::exception); + EXPECT_THROW(value1.as(), std::exception); + + EXPECT_EQ(value2.as(), true); + EXPECT_THROW(value2.as(), std::exception); + EXPECT_THROW(value2.as(), std::exception); + + EXPECT_EQ(value3.as(), -100500); + EXPECT_THROW(value3.as(), std::exception); + EXPECT_THROW(value3.as(), std::exception); + +} + +TEST(AsEnum, Is) +{ + const TestAsEnum value = TestAsEnum::create("test"); + + EXPECT_TRUE(value.is()); + EXPECT_TRUE(value.isStringOpt()); + + EXPECT_FALSE(value.is()); + EXPECT_FALSE(value.isUnknown()); + + EXPECT_FALSE(value.is()); + EXPECT_FALSE(value.isBoolOpt()); +} + +TEST(AsEnum, Switch_Full) +{ + const TestAsEnum value1 = TestAsEnum::createStringOpt("test"); + + value1.doSwitch() + .asCase([] (const std::string& value) { + EXPECT_EQ(value, "test"); + }) + .asCase([] (const bool& value) { + EXPECT_TRUE(false); + }) + .asCase([] (const int& value) { + EXPECT_TRUE(false); + }) + .asDefault([] () { + EXPECT_TRUE(false); + }); +} + +TEST(AsEnum, Switch_Partial) +{ + const TestAsEnum value1 = TestAsEnum::createStringOpt("test"); + + value1.doSwitch() + .asCase([] (const std::string& value) { + EXPECT_EQ(value, "test"); + }) + .asCase([] (const bool& value) { + EXPECT_TRUE(false); + }); +} + +TEST(AsEnum, Switch_Default) +{ + const TestAsEnum value1 = TestAsEnum::createStringOpt("test"); + + value1.doSwitch() + .asCase([] (const int& value) { + EXPECT_TRUE(false); + }) + .asCase([] (const bool& value) { + EXPECT_TRUE(false); + }) + .asDefault([] () { + EXPECT_TRUE(true); + });; }