Scott Langham
Scott Langham

Reputation: 60341

How to pick overload based on return type

In my project enum values often need writing out to log files or to be persisted as strings. So, I've been providing functions ToString and StringToEnum as in the following examples:

namespace Mine
{
    enum class Color { red, green, blue };

    inline std::wstring ToString(Color c)
    {
        switch (c)
        {
        case Color::red: return L"red";
        case Color::green: return L"green";
        case Color::blue: return L"blue";
        default: THROW_MACRO("Unexpected value[{}] for enum[{}]", c, L"Color");
        }
    }

    inline void StringToEnum(const std::wstring& inEnumValueName, Color& out)
    {
        if (inEnumValueName == L"red")
        {
            out = Color::red;
        }
        else if (inEnumValueName == L"green")
        {
            out = Color::green;
        }
        else if (inEnumValueName == L"blue")
        {
            out = Color::blue;
        }
        else
        {
            THROW_MACRO("Unexpected value[{}] for enum[{}]", inEnumValueName, L"Color");
        }
    }
}

When I use StringToEnum I end up writing:

Color c;
StrintToEnum(L"red", c);
// use c

I would really like to be able to declare and initialize on one line and write:

auto c = ToEnum<Mine::Color>(L"red);

I defined ToEnum like this and put it in a header to be included:

namespace CommonCode
{
    template<class T>
    T ToEnum(const std::wstring& enumValueName)
    {
        T value;
        StringToEnum(enumValueName, value);
        return value;
    }
}

The problem is, that ToEnum fails to compile because the relevant StringToEnum functions haven't been defined before it is.

Is there a useful way this can be coded, or am I stuck with needing to write two lines of code whenever I want to declare and initialize an enum value from a string?

I tried to specialize ToEnum, but that suffers from the problem that I need to close the namespace I'm defining the enum in, open the CommonCode namespace and add to that, then go back to original namespace again. This is a lot of typing and looks pretty ugly.

(I'm using Visual Studio 2015 Update 3, so any solutions that compile with that would be preferred).

Upvotes: 0

Views: 80

Answers (3)

Guillaume Racicot
Guillaume Racicot

Reputation: 41750

You have different namespaces. You template declaration is almost correct, but it needs yo know what is StringToEnum. You simply need to put the namespace when you use the function:

namespace CommonCode {
    template<class T>
    T ToEnum(const std::wstring& enumValueName) {
        T value;

        // Notice the namespace here
        Mine::StringToEnum(enumValueName, value);
        return value;
    }
}

What I would recommend is to directly instantiate every function directly:

template<class T>
T toEnum(const std::string&);

template<>
Color toEnum<Color>(const std::string&) {
    if (inEnumValueName == L"red") {
        out = Color::red;
    }

    // ...
}

It will remove boilerplate and let you use your function like you wanted, and is implemented in the right way for that syntax.

Upvotes: 0

Hayt
Hayt

Reputation: 5370

You can use template specialization:

#include <string>

enum class Fail { err };
enum class Color { red,blue,green};

//generic template: causes error in compilation
template<typename T> 
T enumToString(std::string s)
{
    static_assert(sizeof(T) == -1,"no overload for enum");
}

//specialization for Color
template<>
Color enumToString(std::string s)
{
    if(s == "red") 
        return Color::red;
    else if(s == "blue")
        return Color::blue;
    return Color::green;
}

int main()
{

    auto c = enumToString<Color>("blue");
//  auto e = enumToString<Fail>("e"); //this will cause a compiler error
    return static_cast<int>(c);
}

Just specialize the template for each enum you need. I also added a default implementation, which will fail if no specialization is there.

Upvotes: 0

Barry
Barry

Reputation: 302718

StringToEnum should actually return an Enum. As-is, it's somewhat obnoxious to use at best. Since MSVC incorrectly implements two-phase template lookup, your function template solution fails. So instead, let's just pass in something else into StringToEnum that we can use to determine the type. To make it as clear as possible what the purpose of this "something else" is, we'll make it its own type:

template <class T> struct tag { };

In your original example:

inline Color StringToEnum(const std::wstring& inEnumValueName, tag<Color> )
{
    if (inEnumValueName == L"red")
    {
        return Color::red;
    }
    else if (inEnumValueName == L"green")
    {
        return Color::green;
    }
    // etc.
}

So now whenever you want to convert a string to enum E, that's just:

auto e = StringToEnum(str, tag<E>{});

Which could be shortened with a generic overload:

template <class T>
inline T StringToEnum(const std::wstring& name) {
    return StringToEnum(name, tag<T>{} );
}

auto e = StringToEnum<E>(str);

Upvotes: 1

Related Questions