#include <iostream>
#include <type_traits>
#include <map>

template <typename E>
struct enum_traits
{
    static const char* const name;
    static const std::map<E, std::string> mapping;
};

template <typename E>
struct ECategory_impl : std::error_category
{
    static_assert(std::is_enum<E>::value, "!");
    
    const char* name() const noexcept override
    {
      return enum_traits<E>::name;
    }

    std::string message(int c) const override
    {
        const auto& Map = enum_traits<E>::mapping;
        E code = static_cast<E>(c);
        auto itr = Map.find(code);
        if (itr != Map.end())
        {
            return itr->second;
        }
        else
        {
            return "(unrecognized error)";
        }
    }
};

template <typename E>
std::error_code make_error_code(E e)
{
    static_assert(std::is_enum<E>::value, "!");
    static const ECategory_impl<E> categ{};
    return {static_cast<int>(e), categ};
}

#define MAKE_ERROR_CODE_CATEGORY(E) \
     template <> const char* const enum_traits<E>::name = #E; \
namespace std \
{ \
    template <> \
    struct is_error_code_enum<E> : true_type {}; \
} \
using E##_category = ECategory_impl<E>;

enum class E {A, B};
template <> const std::map<E, std::string> enum_traits<E>::mapping
{
    {E::A, "A"},
    {E::B, "E::B"}
};

MAKE_ERROR_CODE_CATEGORY(E)

int main()
{
    auto ec = make_error_code(E::A);
    static_cast<void>(ec);
}
