Bulletmagnet
Bulletmagnet

Reputation: 6010

How can I link a class to an enum?

I have the following code:

#include <string>

enum class Hobbit {
    // typedef HobbitHelper helper;
    UNKNOWN = -1, Bilbo, Frodo, Samwise
};

struct HobbitHelper {
    static Hobbit decode(std::string const& s) {
        if (s == "Bilbo") {
            return Hobbit::Bilbo;
        }
        else if (s == "Frodo") {
            return Hobbit::Frodo;
        }
        else if (s == "Samwise") {
            return Hobbit::Samwise;
        }
        else {
            return Hobbit::UNKNOWN;
        }
    }
};

enum class Wizard {
    // typedef Apprentice helper;
    UNKNOWN = -1, Gandalf, Radagast, Saruman
};

struct Apprentice { // WizardHelper :)
    static Wizard decode(std::string const& s) {
        if (s == "Gandalf") {
            return Wizard::Gandalf;
        }
        else if (s == "Radagast") {
            return Wizard::Radagast;
        }
        else if (s == "Saruman") {
            return Wizard::Saruman;
        }
        else {
            return Wizard::UNKNOWN;
        }
    }
};

template <typename T>
T
decoder(std::string s)
{
    return ??::decode(s);
    // if the typedefs were allowed, I could use T::helper::decode()
}

int main()
{
    std::string s{ "Rincewind" };

    auto h = decoder<Hobbit>(s);
    auto w = decoder<Wizard>(s);
}

How can I arrange to call the appropriate helper class (HobbitHelper or Apprentice) in decoder? I can't declare a nested type inside the enum, as if it was a class. I also tried deriving the helper from the enum (since the helper itself has no data), but that isn't allowed either.

Any ideas?

Upvotes: 0

Views: 193

Answers (5)

Bulletmagnet
Bulletmagnet

Reputation: 6010

In the end I went with a slightly modified version of Barry's answer

template <typename T>
struct enumclass {
};

template<>
struct enumclass<Hobbit> {
    using helper = HobbitHelper;
};

template<>
struct enumclass<Wizard> {
    using helper = Apprentice;
};

because it lets me write the more mnemonic

template <typename T>
T
decoder(std::string s)
{
    return enumclass<T>::helper::decode(s);
}

All the specializations can be distributed (i.e. enumclass <Hobbit> is in hobbit.h; enumclass<Wizard> is in wizard.h). All these headers must include a small header with the unspecialized template.

Upvotes: 0

Barry
Barry

Reputation: 302767

You can just have the helper type trait be external and templated on the enum type, with an explicit specialization for each enum:

template <typename T> struct type_is { using type = T; };

template <typename > struct helper;

template <> struct helper<Hobbit> : type_is<HobbitHelper> { };
template <> struct helper<Wizard> : type_is<Apprentice> { };

template <typename T>
using helper_t = typename helper<T>::type;

And then decode would just access that:

template <typename T>
T decoder(std::string s)
{
    return helper_t<T>::decode(s);
}

Upvotes: 4

Slava
Slava

Reputation: 44238

Aside helper problem there is better solution than cascade if:

static Hobbit decode(std::string const& s) {
    static std::unordered_map<std::strinng,Hobbit> choice {
        { "Bilbo", Hobbit::Bilbo },
        { "Frodo", Hobbit::Frodo },
        { "Samwise", Hobbit::Samwise }
    };
    auto f = choice.find( s );
    return f != choice.end() ? f->second : Hobbit::UNKNOWN;
}

Upvotes: 1

Mark Jansen
Mark Jansen

Reputation: 1509

My suggestion would be partial template specialization, although the answer from @Barry might be more like what you are looking for.

template <typename T>
T decoder(std::string s);

template<>
Hobbit decoder(std::string s)
{
    return HobbitHelper::decode(s);
}

template<>
Wizard decoder(std::string s)
{
    return Apprentice::decode(s);
}

Upvotes: 1

Puppy
Puppy

Reputation: 146910

The simplest way to do it is to use ADL. You can use a type tag to make the compiler look in the appropriate namespace.

Consider:

template<typename T> struct adl_tag {};
namespace MiddleEarth {
    enum class Hobbit {
        // typedef HobbitHelper helper;
        UNKNOWN = -1, Bilbo, Frodo, Samwise
    };
    Hobbit decode(std::string const& s, adl_tag<Hobbit>) {
        if (s == "Bilbo") {
            return Hobbit::Bilbo;
        }
        else if (s == "Frodo") {
            return Hobbit::Frodo;
        }
        else if (s == "Samwise") {
            return Hobbit::Samwise;
        }
        else {
            return Hobbit::UNKNOWN;
        }
    }
}
template<typename T> T decode(std::string s) {
    return decode(s, adl_tag<T>());
}

This is the solution employed by pretty much all C++ libraries- more or less. There's basically no additional effort involved. I didn't even have to mention Wizard.

Upvotes: 1

Related Questions