Skip to content

Commit

Permalink
Added possibility of associating 'void' type. Extended interface.
Browse files Browse the repository at this point in the history
  • Loading branch information
Alkenso committed Jul 25, 2019
1 parent 9613e21 commit 4cab277
Show file tree
Hide file tree
Showing 2 changed files with 314 additions and 87 deletions.
281 changes: 223 additions & 58 deletions include/asenum/asenum.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,105 +26,189 @@

#include <memory>

/// 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<ErrorCode::Unknown>([] (const std::string& value) {
std::cout << "Unknown error: " << value << "\n";
})
.asCase<ErrorCode::Success>([] {
std::cout << "Success\n";
})
.asCase<ErrorCode::Timeout>([] (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<ErrorCode::Unknown>())
{
std::cout << "Unknown error: " << event.asUnknown() << "\n";
}
else if (event.is<ErrorCode::Success>())
{
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<ErrorCode::Timeout>())
{
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<ErrorCode::Unknown>("test.api.com"));
LogSetting(AnyError::create<ErrorCode::Success>());
LogSetting(AnyError::create<ErrorCode::Timeout>(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<enum, name>


#define ASENUM_DEFINE_STRUCTORS() \
#define ASENUM_DEFINE_STRUCTORS_IMPL() \
private: \
using AsEnum::AsEnum; \
\
template <typename T> \
struct AssociatedType { using Type = T; }; \
\
template <AssociatedEnum t_type> \
template <AssociatedEnum T_type> \
struct CaseCast; \
\
public: \
template <AssociatedEnum t_type> \
const typename CaseCast<t_type>::Type& as() const \
template <AssociatedEnum T_type> \
static ThisType create(typename CaseCast<T_type>::Type value) \
{ \
return validatedValueOfType<typename CaseCast<t_type>::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 <AssociatedEnum T_type> \
static ThisType create() \
{ \
return ThisType(T_type); \
} \
\
template <AssociatedEnum T_type> \
const typename CaseCast<T_type>::Type& as() const \
{ \
return validatedValueOfType<typename CaseCast<T_type>::Type>(T_type); \
} \
\
asenum::impl::AsSwitch<AssociatedEnum, ThisType, CaseCast> doSwitch() const \
{ \
return asenum::impl::AsSwitch<AssociatedEnum, ThisType, CaseCast>(*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<AssociatedEnum::case>(std::move(value)); \
} \
\
const type& as##case() const \
{ \
return validatedValueOfType<type>(AssociatedEnum::case); \
} \
\
bool is##case() const \
{ \
return is<AssociatedEnum::case>(); \
} \
\
template <> \
struct CaseCast<AssociatedEnum::case> : AssociatedType<type> {}


#define ASENUM_CASE_VOID_IMPL(case) \
public: \
static ThisType create##case() \
{ \
return create<AssociatedEnum::case>(); \
} \
\
bool is##case() const \
{ \
return is<AssociatedEnum::case>(); \
} \
\
template <> \
struct CaseCast<AssociatedEnum::case> : AssociatedType<void> {}


namespace asenum
{
namespace impl
Expand All @@ -136,13 +220,8 @@ namespace asenum
using AssociatedEnum = Enum;
using ThisType = ConcreteType;

Enum type() const
{
return m_type;
}

template <typename T>
AsEnum(const Enum type, T&& value)
AsEnum(const AssociatedEnum type, T&& value)
: m_type(type)
, m_value(new T(std::forward<T>(value)), [] (void* ptr) {
if (ptr)
Expand All @@ -152,20 +231,106 @@ namespace asenum
})
{}

AsEnum(const AssociatedEnum type)
: m_type(type)
{}

AssociatedEnum type() const
{
return m_type;
}

template <AssociatedEnum T_type>
bool is() const
{
return type() == T_type;
}

template <typename T>
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<const T*>(m_value.get());
}

private:
const Enum m_type;
const AssociatedEnum m_type;
const std::shared_ptr<void> m_value;
};

template <typename Enum, typename ConcreteType, template <Enum t_type> class CaseCast, Enum... types>
class AsSwitch
{
template<Enum...> struct Contains;
template<Enum T_type> struct Contains<T_type> { static const bool value = false; };
template<Enum T_type1, Enum T_type2> struct Contains<T_type1, T_type2> { static const bool value = T_type1 == T_type2; };

template<Enum T_type1, Enum T_type2, Enum... T_other>
struct Contains<T_type1, T_type2, T_other...> { static const bool value = Contains<T_type1, T_type2>::value || Contains<T_type2, T_other...>::value; };

template <Enum T_type, typename Handler, bool isVoid>
struct HandlerCaller;

template <Enum T_type, typename Handler>
struct HandlerCaller<T_type, Handler, true>
{
static void call(const ConcreteType&, const Handler& handler)
{
handler();
}
};

template <Enum T_type, typename Handler>
struct HandlerCaller<T_type, Handler, false>
{
static void call(const ConcreteType& asEnum, const Handler& handler)
{
handler(asEnum.template as<T_type>());
}
};

public:
AsSwitch(const ConcreteType& asEnum, const bool handled) : m_asEnum(asEnum), m_handled(handled) {}
explicit AsSwitch(const ConcreteType& asEnum) : AsSwitch(asEnum, false) {}

template <Enum T_type, typename CaseHandler>
AsSwitch<Enum, ConcreteType, CaseCast, T_type, types...> asCase(const CaseHandler& handler)
{
static_assert(!Contains<T_type, types...>::value, "Duplicated switch case");

if (!m_handled && T_type == m_asEnum.type())
{
m_handled = true;

constexpr bool isVoid = std::is_same<typename CaseCast<T_type>::Type, void>::value;
HandlerCaller<T_type, CaseHandler, isVoid>::call(m_asEnum, handler);
}

return AsSwitch<Enum, ConcreteType, CaseCast, T_type, types...>(m_asEnum, m_handled);
}

template <typename Handler>
void asDefault(const Handler& handler)
{
if (!m_handled)
{
m_handled = true;
handler();
}
}

private:
const ConcreteType& m_asEnum;
bool m_handled;
};
}
}
Loading

0 comments on commit 4cab277

Please sign in to comment.