Scott Langham Scott Langham - 2 months ago 15
C++ Question

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).

Answer

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);