2016-09-12 3 views
0

В моих значениях перечисления проекта часто требуется выписывать файлы журналов или сохраняться в виде строк. Таким образом, я был обеспечение функции ToString и StringToEnum как в следующих примерах:Как выбрать перегрузку на основе типа возврата

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

Когда я использую StringToEnum я заканчиваю писать:

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

Мне бы очень хотелось, чтобы иметь возможность объявить и инициализации в одной строке и написать:

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

Я определил ToEnum, как это и поместить его в заголовок, чтобы быть включены:

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

Проблема заключается в том, что ToEnum не удается скомпилировать, поскольку соответствующие функции StringToEnum не были определены до его появления.

Есть ли полезный способ, которым это может быть закодировано, или я придерживаюсь необходимости писать две строки кода всякий раз, когда я хочу объявить и инициализировать значение перечисления из строки?

Я попытался специализировать ToEnum, но у меня возникла проблема, связанная с тем, что мне нужно закрыть пространство имен. Я определяю enum in, откройте пространство имен CommonCode и добавьте к нему, а затем вернитесь к исходному пространству имен еще раз. Это много печатает и выглядит довольно уродливо.

(Я использую Visual Studio 2015 Update 3, поэтому любые решения, которые скомпилируются с этим, будут предпочтительнее).

+0

Я не уверен, что понимаю, почему вы не можете заставить 'StringToEnum' просто возвращать' Цвет', а не передавать ссылку. – TartanLlama

+0

@TartanLlama, потому что это возможно только для 'Color'. Невозможно было бы использовать для других перечислений. –

+0

Непонятно, почему: a) 'StringToEnum' не может вернуть цвет сам. б) почему он не может быть объявлен перед шаблоном ToEnum. – SergeyA

ответ

1

StringToEnum должен фактически вернуть Enum. Как бы то ни было, в лучшем случае несколько неприятно использовать. Поскольку поиск двухфазного шаблона MSVC incorrectly implements, ваше решение шаблона функции выходит из строя. Поэтому вместо этого давайте просто передадим что-то еще в StringToEnum, которое мы можем использовать для определения типа.Для того, чтобы сделать его как можно более ясно, что цель этого «чего-то другого», мы будем делать это свой собственный тип:

template <class T> struct tag { }; 

В оригинальном примере:

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

Итак, теперь, когда вы хочу, чтобы преобразовать строку в ENUM E, это просто:

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

Какой может быть сокращен с общей перегрузкой:

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

auto e = StringToEnum<E>(str); 
0

У вас разные пространства имен. Вы декларируете шаблон почти правильно, но ему нужно знать, что такое StringToEnum. Вам просто нужно поставить пространство имен при использовании функции:

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

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

То, что я рекомендовал бы непосредственно экземпляр каждую функцию непосредственно:

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

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

    // ... 
} 

Это удалит шаблонное и позволит вам использовать свой как вы хотели, и реализован правильно для этого синтаксиса.

+1

Спасибо, но я не уверен, что это правильно. Я думаю, что C++ найдет эту функцию, даже если она находится в другом пространстве имен из-за зависимого от аргумента поиска, как описано здесь: http://en.cppreference.com/w/cpp/language/adl –

+0

Вы верны. Я отбросил первую часть. –

0

Вы можете использовать шаблон специализации:

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

Просто специализировать шаблон для каждого перечисления вам нужно. Я также добавил реализацию по умолчанию, которая потерпит неудачу, если не существует специализации.