diff --git a/include/.DS_Store b/include/.DS_Store new file mode 100644 index 0000000..c8a52e5 Binary files /dev/null and b/include/.DS_Store differ diff --git a/include/asenum/asenum.h b/include/asenum/asenum.h index 34aa000..15b9f22 100644 --- a/include/asenum/asenum.h +++ b/include/asenum/asenum.h @@ -25,6 +25,7 @@ #pragma once #include +#include namespace asenum { @@ -60,11 +61,11 @@ namespace asenum template class AsMap; - - template + + template class Cmp, typename... T_Cases> struct Comparator; } - + /** @class Associated Enum type. AsEnum should be specialized with single or multiple 'Case/Case11' types that represent associations. @@ -72,6 +73,8 @@ namespace asenum template class AsEnum { + static_assert(sizeof...(T_Cases) > 0, "Failed to instantiate AsEnum with no cases."); + public: /// Related enum type. using Enum = typename details::CaseSet::Enum; @@ -120,41 +123,45 @@ namespace asenum */ template bool ifCase(const Handler& handler) const; - + /** @warning Usually ou don't want to use this method. Use safer 'ifCase'. Force unwraps AsEnum and provides direct access to value that it holds. - + @return Const reference to underlying value. @throws std::invalid_argument exception if 'Case' doesn't correspond to stored case. */ template , typename = typename std::enable_if::value>::type> const R& forceAsCase() const; - + /** Performs switch-like action allowing to wotk with values of different cases. */ details::AsSwitch> doSwitch() const; - + /** Maps (converts) AsEnum value depends on stored case to type 'T'. */ template details::AsMap> doMap() const; - + /** - Compares two AsEnum instances. Instance meant to be equal if and only if + Check for equality two AsEnum instances. Instance meant to be equal if and only if 1) Underlying enum cases are equal; 2) Underlying values are equal. */ bool operator==(const AsEnum& other) const; - + bool operator!=(const AsEnum& other) const; + /** - Compares two AsEnum instances. Instance meant to be equal if and only if - 1) Underlying enum cases are equal; - 2) Underlying values are equal. + Compares two AsEnum instances. + 1) First compare types. If types differ, applies comparison to type itself; + 2) If types equal, compares underlying values. */ - bool operator!=(const AsEnum& other) const; + bool operator<(const AsEnum& other) const; + bool operator<=(const AsEnum& other) const; + bool operator>(const AsEnum& other) const; + bool operator>=(const AsEnum& other) const; private: AsEnum(const Enum relatedCase, std::shared_ptr value); @@ -213,16 +220,16 @@ namespace asenum return AsMap(asEnum, std::move(result)); } }; - - + + 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 class AsSwitch { @@ -240,7 +247,7 @@ namespace asenum const ConcreteAsEnum& m_asEnum; bool m_handled; }; - + template class AsMap { @@ -262,11 +269,11 @@ namespace asenum template > static typename std::enable_if::value, void>::type ifCaseCall(const ConcreteAsEnum& asEnum, std::unique_ptr& result, const Handler& handler); - + template > static typename std::enable_if::value, void>::type ifCaseCall(const ConcreteAsEnum& asEnum, std::unique_ptr& result, const Handler& handler); - + template T ifDefault(const Handler& handler); @@ -316,40 +323,27 @@ namespace asenum using type = typename TypeMap::type; static_assert(!std::is_same::value, "Type is missing for specified enum value."); }; - - template - struct Comparator + + + template class Cmp, typename T_Case> + struct Comparator { template - static bool compare(const ConcreteAsEnum& first, const ConcreteAsEnum& second) - { - return first.template forceAsCase() == second.template forceAsCase(); - } - - template <> - static bool compare(const ConcreteAsEnum&, const ConcreteAsEnum&) - { - return true; - } - - static bool compare(const ConcreteAsEnum& first, const ConcreteAsEnum& second) - { - if (first.enumCase() != T_Case::Code || second.enumCase() != T_Case::Code) - { - return false; - } - - return compare(first, second); - } + static typename std::enable_if::value, bool>::type + compare(const ConcreteAsEnum& first, const ConcreteAsEnum& second); + + template + static typename std::enable_if::value, bool>::type + compare(const ConcreteAsEnum& first, const ConcreteAsEnum& second); + + static bool compare(const ConcreteAsEnum& first, const ConcreteAsEnum& second, bool& finalVerdict); + static bool compare(const ConcreteAsEnum& first, const ConcreteAsEnum& second); }; - - template - struct Comparator + + template class Cmp, typename T_Case, typename... T_Cases> + struct Comparator { - static bool compare(const ConcreteAsEnum& first, const ConcreteAsEnum& second) - { - return Comparator::compare(first, second) || Comparator::compare(first, second); - } + static bool compare(const ConcreteAsEnum& first, const ConcreteAsEnum& second); }; } } @@ -415,7 +409,7 @@ const R& asenum::AsEnum::forceAsCase() const { throw std::invalid_argument("Unwrapping case does not correspond to stored case."); } - + return *reinterpret_cast*>(m_value.get()); } @@ -435,13 +429,37 @@ asenum::details::AsMap::Enum, asenum::AsE template bool asenum::AsEnum::operator==(const AsEnum& other) const { - return details::Comparator::compare(*this, other); + return details::Comparator::compare(*this, other); } template bool asenum::AsEnum::operator!=(const AsEnum& other) const { - return !(*this == other); + return details::Comparator::compare(*this, other); +} + +template +bool asenum::AsEnum::operator<(const AsEnum& other) const +{ + return details::Comparator::compare(*this, other); +} + +template +bool asenum::AsEnum::operator<=(const AsEnum& other) const +{ + return details::Comparator::compare(*this, other); +} + +template +bool asenum::AsEnum::operator>(const AsEnum& other) const +{ + return details::Comparator::compare(*this, other); +} + +template +bool asenum::AsEnum::operator>=(const AsEnum& other) const +{ + return details::Comparator::compare(*this, other); } @@ -550,11 +568,75 @@ template R asenum::details::AsMap::ifCase(const CaseHandler& handler) { static_assert(!Contains::value, "Duplicated map case"); - + if (!m_result) { ifCaseCall(m_asEnum, m_result, handler); } - + return ResultMaker::template makeResult(m_asEnum, std::move(m_result)); } + +// Private details - Comparator + +template class Cmp, typename T_Case> +bool asenum::details::Comparator::compare(const ConcreteAsEnum& first, const ConcreteAsEnum& second, bool& finalVerdict) +{ + // If AsEnums cases are NOT equal. + if (first.enumCase() != second.enumCase()) + { + finalVerdict = true; + + static constexpr Cmp s_comparator; + return s_comparator(first.enumCase(), second.enumCase()); + } + + // Both AsEnums cases are equal. Check if they are equal to Comparator's case. + if (first.enumCase() == T_Case::Code) + { + finalVerdict = true; + + return compare(first, second); + } + + return false; +} + +template class Cmp, typename T_Case> +bool asenum::details::Comparator::compare(const ConcreteAsEnum& first, const ConcreteAsEnum& second) +{ + bool finalVerdict = false; + return compare(first, second, finalVerdict); +} + +template class Cmp, typename T_Case> +template +typename std::enable_if::value, bool>::type +asenum::details::Comparator::compare(const ConcreteAsEnum& first, const ConcreteAsEnum& second) +{ + using UT = typename ConcreteAsEnum::template UnderlyingType; + static constexpr Cmp s_comparator; + return s_comparator(first.template forceAsCase(), second.template forceAsCase()); +} + +template class Cmp, typename T_Case> +template +typename std::enable_if::value, bool>::type +asenum::details::Comparator::compare(const ConcreteAsEnum&, const ConcreteAsEnum&) +{ + static constexpr Cmp s_comparator; + return s_comparator(true, true); +} + +template class Cmp, typename T_Case, typename... T_Cases> +bool asenum::details::Comparator::compare(const ConcreteAsEnum& first, const ConcreteAsEnum& second) +{ + bool finalVerdict = false; + const bool result = Comparator::compare(first, second, finalVerdict); + if (finalVerdict) + { + return result; + } + + return Comparator::compare(first, second); +} diff --git a/tests/AsEnumTest.cpp b/tests/AsEnumTest.cpp index 3236f1a..bf451c8 100644 --- a/tests/AsEnumTest.cpp +++ b/tests/AsEnumTest.cpp @@ -34,32 +34,44 @@ namespace { enum class TestEnum { - Unknown, - StringOpt, - VoidOpt + StringOpt1, + VoidOpt2, + Unknown3 }; using TestAsEnum = asenum::AsEnum< - asenum::Case11, - asenum::Case11, - asenum::Case11 + asenum::Case11, + asenum::Case11, + asenum::Case11 >; - static_assert(std::is_same, int>::value, "Invalid underlying type"); - static_assert(std::is_same, std::string>::value, "Invalid underlying type"); - static_assert(std::is_same, void>::value, "Invalid underlying type"); + static_assert(std::is_same, int>::value, "Invalid underlying type"); + static_assert(std::is_same, std::string>::value, "Invalid underlying type"); + static_assert(std::is_same, void>::value, "Invalid underlying type"); static_assert(GTEST_ARRAY_SIZE_(TestAsEnum::AllCases) == 3, "Invalid number of cases"); - static_assert(TestAsEnum::AllCases[0] == TestEnum::Unknown, "Invalid enum case"); - static_assert(TestAsEnum::AllCases[1] == TestEnum::StringOpt, "Invalid enum case"); - static_assert(TestAsEnum::AllCases[2] == TestEnum::VoidOpt, "Invalid enum case"); + static_assert(TestAsEnum::AllCases[0] == TestEnum::Unknown3, "Invalid enum case"); + static_assert(TestAsEnum::AllCases[1] == TestEnum::StringOpt1, "Invalid enum case"); + static_assert(TestAsEnum::AllCases[2] == TestEnum::VoidOpt2, "Invalid enum case"); + + + enum class SomeVoidEnum + { + Opt1, + Opt2 + }; + + using SomeVoidAsEnum = asenum::AsEnum< + asenum::Case11, + asenum::Case11 + >; } TEST(AsEnum, IfCase) { - const TestAsEnum value1 = TestAsEnum::create("test"); - const TestAsEnum value2 = TestAsEnum::create(); - const TestAsEnum value3 = TestAsEnum::create(-100500); + const TestAsEnum value1 = TestAsEnum::create("test"); + const TestAsEnum value2 = TestAsEnum::create(); + const TestAsEnum value3 = TestAsEnum::create(-100500); MockFunction handler1; MockFunction handler2; @@ -72,45 +84,45 @@ TEST(AsEnum, IfCase) EXPECT_CALL(handler3, Call(-100500)) .WillOnce(Return()); - EXPECT_TRUE(value1.ifCase(handler1.AsStdFunction())); - EXPECT_FALSE(value1.ifCase(handler2.AsStdFunction())); - EXPECT_FALSE(value1.ifCase(handler3.AsStdFunction())); + EXPECT_TRUE(value1.ifCase(handler1.AsStdFunction())); + EXPECT_FALSE(value1.ifCase(handler2.AsStdFunction())); + EXPECT_FALSE(value1.ifCase(handler3.AsStdFunction())); - EXPECT_FALSE(value2.ifCase(handler1.AsStdFunction())); - EXPECT_TRUE(value2.ifCase(handler2.AsStdFunction())); - EXPECT_FALSE(value2.ifCase(handler3.AsStdFunction())); + EXPECT_FALSE(value2.ifCase(handler1.AsStdFunction())); + EXPECT_TRUE(value2.ifCase(handler2.AsStdFunction())); + EXPECT_FALSE(value2.ifCase(handler3.AsStdFunction())); - EXPECT_FALSE(value3.ifCase(handler1.AsStdFunction())); - EXPECT_FALSE(value3.ifCase(handler2.AsStdFunction())); - EXPECT_TRUE(value3.ifCase(handler3.AsStdFunction())); + EXPECT_FALSE(value3.ifCase(handler1.AsStdFunction())); + EXPECT_FALSE(value3.ifCase(handler2.AsStdFunction())); + EXPECT_TRUE(value3.ifCase(handler3.AsStdFunction())); } TEST(AsEnum, IsCase) { - const TestAsEnum value1 = TestAsEnum::create("test"); - const TestAsEnum value2 = TestAsEnum::create(); - const TestAsEnum value3 = TestAsEnum::create(-100500); + const TestAsEnum value1 = TestAsEnum::create("test"); + const TestAsEnum value2 = TestAsEnum::create(); + const TestAsEnum value3 = TestAsEnum::create(-100500); - EXPECT_EQ(value1.enumCase(), TestEnum::StringOpt); - EXPECT_EQ(value2.enumCase(), TestEnum::VoidOpt); - EXPECT_EQ(value3.enumCase(), TestEnum::Unknown); + EXPECT_EQ(value1.enumCase(), TestEnum::StringOpt1); + EXPECT_EQ(value2.enumCase(), TestEnum::VoidOpt2); + EXPECT_EQ(value3.enumCase(), TestEnum::Unknown3); - EXPECT_TRUE(value1.isCase()); - EXPECT_FALSE(value1.isCase()); - EXPECT_FALSE(value1.isCase()); + EXPECT_TRUE(value1.isCase()); + EXPECT_FALSE(value1.isCase()); + EXPECT_FALSE(value1.isCase()); - EXPECT_FALSE(value2.isCase()); - EXPECT_TRUE(value2.isCase()); - EXPECT_FALSE(value2.isCase()); + EXPECT_FALSE(value2.isCase()); + EXPECT_TRUE(value2.isCase()); + EXPECT_FALSE(value2.isCase()); - EXPECT_FALSE(value3.isCase()); - EXPECT_FALSE(value3.isCase()); - EXPECT_TRUE(value3.isCase()); + EXPECT_FALSE(value3.isCase()); + EXPECT_FALSE(value3.isCase()); + EXPECT_TRUE(value3.isCase()); } TEST(AsEnum, Switch_Full) { - const TestAsEnum value = TestAsEnum::create("test"); + const TestAsEnum value = TestAsEnum::create("test"); MockFunction handler; @@ -118,11 +130,11 @@ TEST(AsEnum, Switch_Full) .WillOnce(Return()); value.doSwitch() - .ifCase(handler.AsStdFunction()) - .ifCase([] { + .ifCase(handler.AsStdFunction()) + .ifCase([] { EXPECT_TRUE(false); }) - .ifCase([] (const int& value) { + .ifCase([] (const int& value) { EXPECT_TRUE(false); }) .ifDefault([] () { @@ -132,7 +144,7 @@ TEST(AsEnum, Switch_Full) TEST(AsEnum, Switch_Partial) { - const TestAsEnum value = TestAsEnum::create("test"); + const TestAsEnum value = TestAsEnum::create("test"); MockFunction handler; @@ -140,15 +152,15 @@ TEST(AsEnum, Switch_Partial) .WillOnce(Return()); value.doSwitch() - .ifCase(handler.AsStdFunction()) - .ifCase([] { + .ifCase(handler.AsStdFunction()) + .ifCase([] { EXPECT_TRUE(false); }); } TEST(AsEnum, Switch_Default) { - const TestAsEnum value = TestAsEnum::create("test"); + const TestAsEnum value = TestAsEnum::create("test"); MockFunction handler; @@ -156,10 +168,10 @@ TEST(AsEnum, Switch_Default) .WillOnce(Return()); value.doSwitch() - .ifCase([] (const int& value) { + .ifCase([] (const int& value) { EXPECT_TRUE(false); }) - .ifCase([] { + .ifCase([] { EXPECT_TRUE(false); }) .ifDefault(handler.AsStdFunction()); @@ -167,13 +179,13 @@ TEST(AsEnum, Switch_Default) TEST(AsEnum, Map_With_Default) { - const TestAsEnum value = TestAsEnum::create("test"); + const TestAsEnum value = TestAsEnum::create("test"); const bool vv = value.doMap() - .ifCase([] (const std::string& value) { + .ifCase([] (const std::string& value) { return true; }) - .ifCase([] { + .ifCase([] { return false; }) .ifDefault([] { @@ -185,16 +197,16 @@ TEST(AsEnum, Map_With_Default) TEST(AsEnum, Map_All_Cases) { - const TestAsEnum value = TestAsEnum::create("test"); + const TestAsEnum value = TestAsEnum::create("test"); const bool vv = value.doMap() - .ifCase([] (const int& value) { + .ifCase([] (const int& value) { return false; }) - .ifCase([] { + .ifCase([] { return false; }) - .ifCase([] (const std::string& value) { + .ifCase([] (const std::string& value) { return true; }); @@ -203,24 +215,24 @@ TEST(AsEnum, Map_All_Cases) TEST(AsEnum, ForceAsCase) { - const TestAsEnum value1 = TestAsEnum::create("test"); + const TestAsEnum value1 = TestAsEnum::create("test"); // TestEnum::VoidOpt doesn't have 'forceAsEnum' method because associated type is 'void'. - const TestAsEnum value3 = TestAsEnum::create(-100500); + const TestAsEnum value3 = TestAsEnum::create(-100500); - EXPECT_EQ(value1.forceAsCase(), "test"); - EXPECT_THROW(value1.forceAsCase(), std::invalid_argument); + EXPECT_EQ(value1.forceAsCase(), "test"); + EXPECT_THROW(value1.forceAsCase(), std::invalid_argument); - EXPECT_THROW(value3.forceAsCase(), std::invalid_argument); - EXPECT_EQ(value3.forceAsCase(), -100500); + EXPECT_THROW(value3.forceAsCase(), std::invalid_argument); + EXPECT_EQ(value3.forceAsCase(), -100500); } -TEST(AsEnum, Compare) +TEST(AsEnum, Equality) { - const TestAsEnum value1 = TestAsEnum::create("test"); - const TestAsEnum value2 = TestAsEnum::create("test"); - const TestAsEnum value3 = TestAsEnum::create("test2"); - const TestAsEnum value4 = TestAsEnum::create(); - const TestAsEnum value5 = TestAsEnum::create(-100500); + const TestAsEnum value1 = TestAsEnum::create("test"); + const TestAsEnum value2 = TestAsEnum::create("test"); + const TestAsEnum value3 = TestAsEnum::create("test2"); + const TestAsEnum value4 = TestAsEnum::create(); + const TestAsEnum value5 = TestAsEnum::create(-100500); EXPECT_EQ(value1, value1); EXPECT_EQ(value1, value2); @@ -228,5 +240,67 @@ TEST(AsEnum, Compare) EXPECT_NE(value1, value4); EXPECT_NE(value1, value5); - EXPECT_EQ(value4, TestAsEnum::create()); + EXPECT_EQ(value4, TestAsEnum::create()); +} + +TEST(AsEnum, Equality_Void) +{ + const SomeVoidAsEnum value1 = SomeVoidAsEnum::create(); + const SomeVoidAsEnum value2 = SomeVoidAsEnum::create(); + const SomeVoidAsEnum value3 = SomeVoidAsEnum::create(); + + EXPECT_EQ(value1, value1); + EXPECT_EQ(value1, value2); + EXPECT_NE(value1, value3); +} + +TEST(AsEnum, Compare_SameCase) +{ + const TestAsEnum value1 = TestAsEnum::create("test"); + const TestAsEnum value2 = TestAsEnum::create("test"); + const TestAsEnum value3 = TestAsEnum::create("test2"); + + EXPECT_LT(value1, value3); + EXPECT_LE(value1, value2); + EXPECT_GT(value3, value1); + EXPECT_GE(value1, value2); +} + +TEST(AsEnum, Compare_SameCase_Void) +{ + const SomeVoidAsEnum value1 = SomeVoidAsEnum::create(); + const SomeVoidAsEnum value2 = SomeVoidAsEnum::create(); + const SomeVoidAsEnum value3 = SomeVoidAsEnum::create(); + + EXPECT_LT(value1, value3); + EXPECT_LE(value1, value2); + EXPECT_GT(value3, value1); + EXPECT_GE(value1, value2); +} + +TEST(AsEnum, Compare_RandomCase) +{ + const TestAsEnum value1 = TestAsEnum::create("test"); + const TestAsEnum value2 = TestAsEnum::create(); + const TestAsEnum value3 = TestAsEnum::create(-100500); + + EXPECT_LT(value1, value2); + EXPECT_LT(value2, value3); + + EXPECT_LE(value1, value1); + EXPECT_LE(value1, value2); + EXPECT_LE(value1, value3); + EXPECT_LE(value2, value2); + EXPECT_LE(value2, value3); + EXPECT_LE(value3, value3); + + EXPECT_GT(value3, value2); + EXPECT_GT(value3, value1); + + EXPECT_GE(value3, value3); + EXPECT_GE(value3, value2); + EXPECT_GE(value3, value1); + EXPECT_GE(value2, value2); + EXPECT_GE(value2, value1); + EXPECT_GE(value1, value1); }