diff --git a/include/asenum/asenum.h b/include/asenum/asenum.h index 48d74cb..5e3f2d2 100644 --- a/include/asenum/asenum.h +++ b/include/asenum/asenum.h @@ -26,251 +26,124 @@ #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) - - -/** - enum class ErrorCode - { - Unknown, - Success, - Timeout - }; - - // Associate enum 'ErrorCode' with AsEnum 'AnyError' - ASENUM_DECLARE(AnyError, ErrorCode) - { - ASENUM_DEFINE_STRUCTORS(); - - ASENUM_CASE(Unknown, std::string); - ASENUM_CASE_VOID(Success); - ASENUM_CASE(Timeout, std::chrono::seconds); - }; - - //===== USAGE ===== - void LogError(const AnyError& event) - { - 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()) - { - std::cout << "Success\n"; - } - else if (event.is()) - { - std::cout << "Timed out after: " << event.asTimeout().count() << "\n"; - } - } - - int main() - { - // ===== CREATION ===== - LogError(AnyError::createUnknown("test.api.com")); - LogError(AnyError::createSuccess()); - LogError(AnyError::createTimeout(std::chrono::seconds(1))); - - // === vs === - - LogError(AnyError::create("test.api.com")); - LogError(AnyError::create()); - LogError(AnyError::create(std::chrono::seconds(1))); - - return 0; - } - */ - - - -// Private implementation details - - -#define ASENUM_DECLARE_IMPL(name, enum) \ -class name: protected asenum::impl::AsEnum - - -#define ASENUM_DEFINE_STRUCTORS_IMPL() \ -private: \ - using AsEnum::AsEnum; \ - \ - template \ - struct AssociatedType { using Type = T; }; \ - \ - template \ - struct CaseCast; \ - \ -public: \ - template \ - using UnderlyingType = typename CaseCast::Type; \ - \ - template \ - static ThisType create(UnderlyingType value) \ - { \ - return ThisType(T_type, std::move(value)); \ - } \ - \ - template \ - static ThisType create() \ - { \ - return ThisType(T_type); \ - } \ - \ - template \ - const UnderlyingType& as() const \ - { \ - return validatedValueOfType::Type>(T_type); \ - } \ - \ - asenum::impl::AsSwitch doSwitch() const \ - { \ - return asenum::impl::AsSwitch(*this); \ - } \ - \ - using AsEnum::type; \ - using AsEnum::is - - -#define ASENUM_CASE_IMPL(case, type) \ -public: \ - static ThisType create##case(type value) \ - { \ - return create(std::move(value)); \ - } \ - \ - const type& as##case() const \ - { \ - 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 + /** + @class Case descriptor of single Associated Enum case. + */ + template + struct Case11 { - template - class AsEnum - { - public: - using AssociatedEnum = Enum; - using ThisType = ConcreteType; - - template - AsEnum(const AssociatedEnum type, T&& value) - : m_type(type) - , m_value(new T(std::forward(value)), [] (void* ptr) { - if (ptr) - { - delete reinterpret_cast(ptr); - } - }) - {} - - 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 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 AssociatedEnum m_type; - const std::shared_ptr m_value; - }; + using Enum = T_Enum; + static constexpr Enum Code = T_Code; + using Type = T; + }; + +#if __cplusplus > 201402L + /** + @class Case descriptor of single Associated Enum case. Convenient use with C++17 compiler. + */ + template + using Case = Case11; +#endif + + namespace details + { + template + struct CaseSet; + + template + struct UnderlyingTypeResolver; + + template + class AsSwitch; + } + + /** + @class Associated Enum type. + AsEnum should be specialized with single or multiple 'Case/Case11' types that represent associations. + */ + template + class AsEnum + { + public: + /// Related enum type. + using Enum = typename details::CaseSet::Enum; + + /// Specific 'using' that allows to get accociated type with specific enum case. + template + using UnderlyingType = typename details::UnderlyingTypeResolver::type; + + /// Array of all cases used associated with concrete AsEnum. + static constexpr Enum AllCases[] = { T_Cases::Code... }; + + /** + Creates AsEnum instance of specific case. + @param value Value related to specified enum case. + @return AsEnum instance holding value of specified case. + */ + template , void>::value>::type> + static AsEnum create(T value); + + /** + Creates AsEnum instance of specific case with 'void' associated type. + @return AsEnum instance holding value of specified case. + */ + template , void>::value>::type> + static AsEnum create(); + + /** + @return enum case of current instance of AsEnum. + */ + Enum enumCase() const; + + /** + @return Boolean indicates if current instance of AsEnum holds exactly specified case...or not. + */ + template + bool isCase() const; + +#error add example + /** + Unwraps AsEnum and provides access to value that it holds. + + @param handler Function or functional object of signature void(UnderlyingType) + that accepts value/reference of type associated with specified case. + @return Boolean indicates if handler has been called. + */ + template + bool ifCase(const Handler& handler) const; + +#error add example + /** + Performs switch-like action allowing to wotk with values of different cases. + + */ + details::AsSwitch> doSwitch() const; + + private: + template ::type> + AsEnum(const Enum relatedCase, T&& value); + + explicit AsEnum(const Enum relatedCase); + + template + static typename std::enable_if::value, void>::type call(const void*, const Handler& handler); - template class CaseCast, Enum... types> + template + static typename std::enable_if::value, void>::type call(const void* value, const Handler& handler); + + private: + const Enum m_enumCase; + const std::shared_ptr m_value; + }; + + + // Private details + + namespace details + { + template class AsSwitch { template struct Contains; @@ -280,60 +153,186 @@ namespace asenum 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) {} + AsSwitch(const ConcreteAsEnum& asEnum, const bool handled); + explicit AsSwitch(const ConcreteAsEnum& asEnum); 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); - } + AsSwitch ifCase(const CaseHandler& handler); template - void asDefault(const Handler& handler) - { - if (!m_handled) - { - m_handled = true; - handler(); - } - } + void ifDefault(const Handler& handler); private: - const ConcreteType& m_asEnum; + const ConcreteAsEnum& m_asEnum; bool m_handled; }; + + + template + struct CaseSet + { + using Enum = typename Case::Enum; + static_assert(std::is_enum::value, "All cases must relate to enum values."); + }; + + template + struct CaseSet + { + static_assert(std::is_same::Enum, typename CaseSet::Enum>::value, "All cases must relate to the same enum."); + using Enum = typename CaseSet::Enum; + }; + + + template + struct UnderlyingTypeResolver + { + template + struct TypeMap; + + template + struct TypeMap + { + using type = typename std::conditional::type>::type; + }; + + template + struct TypeMap + { + using type = typename std::conditional::type; + }; + + + struct Dummy {}; + using type = typename TypeMap::type; + static_assert(!std::is_same::value, "Type is missing for specified enum value."); + }; + + } +} + + +// AsEnum public + +template +constexpr typename asenum::AsEnum::Enum asenum::AsEnum::AllCases[]; + +template +template ::Enum Case, typename T> +asenum::AsEnum asenum::AsEnum::create(T value) +{ + return asenum::AsEnum(Case, value); +} + +template +template ::Enum Case, typename T> +asenum::AsEnum asenum::AsEnum::create() +{ + return asenum::AsEnum(Case); +} + +template +typename asenum::AsEnum::Enum asenum::AsEnum::enumCase() const +{ + return m_enumCase; +} + +template +template ::Enum Case> +bool asenum::AsEnum::isCase() const +{ + return Case == m_enumCase; +} + +template +template ::Enum Case, typename Handler> +bool asenum::AsEnum::ifCase(const Handler& handler) const +{ + const bool isType = isCase(); + if (isType) + { + call>(m_value.get(), handler); + } + + return isType; +} + +template +asenum::details::AsSwitch::Enum, asenum::AsEnum> asenum::AsEnum::doSwitch() const +{ + return details::AsSwitch>(*this); +} + + +// AsEnum private + +template +template +asenum::AsEnum::AsEnum(const Enum relatedCase, T&& value) +: m_enumCase(relatedCase) +, m_value(new RealT(std::forward(value)), [] (void* ptr) { + if (ptr) + { + delete reinterpret_cast(ptr); + } +}) +{} + +template +asenum::AsEnum::AsEnum(const Enum relatedCase) +: m_enumCase(relatedCase) +, m_value(nullptr) +{} + +template +template +typename std::enable_if::value, void>::type asenum::AsEnum::call(const void*, const Handler& handler) +{ + handler(); +} + +template +template +typename std::enable_if::value, void>::type asenum::AsEnum::call(const void* value, const Handler& handler) +{ + handler(*reinterpret_cast(value)); +} + + +// Private details + +template +asenum::details::AsSwitch::AsSwitch(const ConcreteAsEnum& asEnum, const bool handled) +: m_asEnum(asEnum) +, m_handled(handled) +{} + +template +asenum::details::AsSwitch::AsSwitch(const ConcreteAsEnum& asEnum) +: AsSwitch(asEnum, false) +{} + +template +template +asenum::details::AsSwitch asenum::details::AsSwitch::ifCase(const CaseHandler& handler) +{ + static_assert(!Contains::value, "Duplicated switch case"); + + if (!m_handled) + { + m_handled = m_asEnum.template ifCase(handler); + } + + return AsSwitch(m_asEnum, m_handled); +} + +template +template +void asenum::details::AsSwitch::ifDefault(const Handler& handler) +{ + if (!m_handled) + { + m_handled = true; + handler(); } }