我具有以下冗余感觉设计,可在enum与存储enum的类的字符串之间进行转换。如果存在更多的enum,则该方法无法扩展,并且无论如何,冗余度较小的代码也更好。

问题


如果存在如果使用更多enum,则有可能避免为每个枚举类型和设备定义两个显式转换函数,而在该系统中,调用者只能看到一个(即转换)或两个不同的函数名(即,所有converttoconvertfrom / enum,而不仅仅是每个enum类型)?也许对autodecltype使用某种演绎法术?它看起来像模棱两可,因为只有返回值才可用于分隔不同的函数重载(即使使用函数模板也是如此)。
以下设计是分离转换函数并将其置于匿名namespace设计(我曾考虑过将转换函数放到一个文件中,例如conversions.incl并包含它)?

这个想法将是倍数(即,比这里显示的enum更多)尽可能隐式的转换

将使用这样的转换:


string token_string = "none"; //In reality this will be externally, user, generated.
some_class_instance->set_type(enum_conversion(token_string));
token_string = enum_conversion(some_class_instance->get_type());


呈现一个enum及其相关转换(但可能会有更多转换):

some_class.h

class some_class
{
    public:
         enum class enum_type
         {
             none   = 0,
             type1  = 1,
             type2  = 2
         }

     void set_type(enum_type);
     enum_type get_type() const;

   private:
       enum_type type_;
};

namespace
{
    std::array<std::pair<std::string, some_class::enume_type>, 3> type_map;

    bool initialize_map()
    {
       type_map[0] = std::make_pair("none", some_class::enum_type::none);
       type_map[1] = std::make_pair("type1", some_class::enum_type::type1);
       type_map[2] = std::make_pair("type2", some_class::enum_type::type2);
    }

    bool initialization_result = initialize_map();

    some_class::enum_type enum_conversion(std::string const& enum_type)
    {
        for(auto val: type_map)
        {
            if(val.first == enum_type)
            {
                return val.second;
            }
        }

        return type_map[0].second;
    }

    std::string enum_conversion(some_class::enum_type enum_type)
    {
        for(auto val: type_map)
        {
            if(val.second == enum_type)
            {
                return val.first;
            }
        }

        return type_parameter_map[0].first;
    }
}


#1 楼

我将使用一些模板逻辑来以更可扩展的方式实现效果:

#include <iostream>
#include <sstream>
#include <string>
#include <algorithm>

// This is the type that will hold all the strings.
// Each enumeration type will declare its own specialization.
// Any enum that does not have a specialization will generate a compiler error
// indicating that there is no definition of this variable (as there should be
// be no definition of a generic version).
template<typename T>
struct enumStrings
{
    static char const* data[];
};

// This is a utility type.
// Created automatically. Should not be used directly.
template<typename T>
struct enumRefHolder
{
    T& enumVal;
    enumRefHolder(T& enumVal): enumVal(enumVal) {}
};
template<typename T>
struct enumConstRefHolder
{
    T const& enumVal;
    enumConstRefHolder(T const& enumVal): enumVal(enumVal) {}
};

// The next two functions do the actual work of reading/writing an
// enum as a string.
template<typename T>
std::ostream& operator<<(std::ostream& str, enumConstRefHolder<T> const& data)
{
   return str << enumStrings<T>::data[data.enumVal];
}

template<typename T>
std::istream& operator>>(std::istream& str, enumRefHolder<T> const& data)
{
    std::string value;
    str >> value;

    // These two can be made easier to read in C++11
    // using std::begin() and std::end()
    //  
    static auto begin  = std::begin(enumStrings<T>::data);
    static auto end    = std::end(enumStrings<T>::data);

    auto find   = std::find(begin, end, value);
    if (find != end)
    {   
        data.enumVal = static_cast<T>(std::distance(begin, find));
    }   
    return str;
}


// This is the public interface:
// use the ability of function to deduce their template type without
// being explicitly told to create the correct type of enumRefHolder<T>
template<typename T>
enumConstRefHolder<T>  enumToString(T const& e) {return enumConstRefHolder<T>(e);}

template<typename T>
enumRefHolder<T>       enumFromString(T& e)     {return enumRefHolder<T>(e);}


然后您可以像这样使用它:

// Define Enum Like this
enum X {Hi, Lo};
// Then you just need to define their string values.
template<> char const* enumStrings<X>::data[] = {"Hi", "Lo"};

int main()
{
    X   a=Hi;

    std::cout << enumToString(a) << "\n";

    std::stringstream line("Lo");
    line >> enumFromString(a);

    std::cout << "A: " << a << " : " << enumToString(a) << "\n";
}


评论


\ $ \ begingroup \ $
我觉得这种设计要优越得多。我尝试了类似的方法,但是遇到了专业化方面的问题(因此,我对专业化重载的评论)。然后我进入了我可以编译的第二好的选择。简单的数组感觉就足够了(也许是最快的),因为它们很短,最多大约十个项目。但是无论如何,感谢您的代码和注意,这是一个很好的学习经历!
\ $ \ endgroup \ $
–Veksi
2012年8月4日在7:48

\ $ \ begingroup \ $
还有一个问题,如果仍然可以的话,如何在不使用流的情况下定义转换?例如。 template T convertTo(std :: string const&token){std :: stringstream line(token); a行>> enumFromString(a);返回},但这并不是最直接的解决方案。另外,由于某种原因,我似乎找不到找到从另一个枚举生成字符串的方法。 }
\ $ \ endgroup \ $
–Veksi
2012年8月4日在12:27

\ $ \ begingroup \ $
嗯...此外,现在,当我尝试更多时,看来ostream转换无法编译。我的VS 2012 RC失败,并显示错误消息“错误1错误C2440:'':无法从'const some_namespace :: some_class :: X'转换为'some_namespace ::`anonymous-namespace':: enumConstRefHolder '“和错误C2677:二进制'[':找不到类型为'const some_namespace :: some_class :: X'(或没有可接受的转换)的全局运算符”,而且我还是菜鸟般无法解决此错误发消息给自己,我聚集。我还能帮你一会儿吗..?:)
\ $ \ endgroup \ $
–Veksi
2012年8月4日14:13



\ $ \ begingroup \ $
我想我发现了问题的症结所在:我的枚举是强类型的,因此它们不能用于按原样对数组进行索引,而是采用类似“ return str << enumStrings < T> :: data [static_cast (data.enumValue_)];“那么EnumConstRefHolder的构造函数也需要通过常量引用来获取参数。看起来强类型枚举可以转换为整数,并且无需指定其基础类型即可工作。不过,最好指定一个。
\ $ \ endgroup \ $
–Veksi
2012年8月4日在16:06



\ $ \ begingroup \ $
要仍然添加注释(如果有人想读到目前为止),默认的基础类型是int,但是可以更改,因此在编译过程中“审问”是更安全的。可以使用std :: underlying_type进行转换,例如“ return str << enumStrings :: data [static_cast :: type>(data.enumValue)”。
\ $ \ endgroup \ $
–Veksi
2012年8月4日在16:24



#2 楼

在大多数情况下,在c ++枚举和字符串表示之间进行转换的要求是由于与不理解您的枚举声明的另一个程序的接口而引起的。通常,另一个程序将是SQL数据库。

因此,我将使用代码生成器来确保整个系统的一致性。

代码生成器可以遍历整个数据库为数据库中可能包含枚举语义的所有内容编制目录并创建枚举c头和相应的字符串数组。编写这样的生成器要花上几天的时间。

请注意,这种方法不仅使您不必编写字符串部分。它还使您不必完全编写枚举定义。

评论


\ $ \ begingroup \ $
这是我用于嵌入式固件编程的方法。我使用功能最强的表示语言(例如C#),并直接从带有属性注释的C#代码生成所有较不复杂的语言(例如C或C ++)的标头。
\ $ \ endgroup \ $
–马克·拉卡塔(Mark Lakata)
14年6月20日在22:20

#3 楼


这里是一个模板类,使您可以以字符串形式写入和读取枚举类成员。这是Loki Astari设计的简化。通过使用enable_if<>is_enum<>模板,它避免了Veski所建议的辅助功能。

该想法是用

template <typename T,
    typename std::enable_if<std::is_enum<T>::value>::type* = nullptr>


结果是,仅对枚举实例化了template <T>operator<<()模板(因为“替换失败不是错误”(SFINAE)原理)。然后就不再需要辅助类operator>>()enumConstRefHolder以及函数enumRefHolderenumToString()

除了通过字符串识别枚举成员(标准化为全大写字母)之外,代码还识别其整数表示形式。 (对于下面的示例,enumFromString()"FaST"都将读为"1"。)

EnumIO.h:

#ifndef ENUMIO_H_
#define ENUMIO_H_

#include <algorithm>
#include <ios>
#include <iostream>
#include <sstream>
#include <vector>

// A template class that enables writing and reading enum class
// members as strings.
//
// Author:  Bradley Plohr (2017-05-12)
//
// Note:  The idea to keep the enum names as a static member in a
// template comes from Loki Astari:
//
// https://codereview.stackexchange.com/questions/14309
//         /conversion-between-enum-and-string-in-c-class-header
//
// Usage example:
//
// Enums.h:
// -------
// #ifndef ENUMS_H_
// #define ENUMS_H_
//
// enum class Family { SLOW, FAST };
//
// TODO:  other enum classes
//
// #endif /* ENUMS_H_ */
//
//
// Enums.cc:
// --------
// #include "Enums.h"
// #include "EnumIO.h"
// #include <string>
// #include <vector>
//
// template <>
// const std::vector<std::string>& EnumIO<Family>::enum_names()
// {
//      static std::vector<std::string> enum_names_({ "SLOW", "FAST" });
//      return enum_names_;
// }
//
// TODO:  enum names for other enum classes
//
//
// t_EnumIO.cc:
// -----------
// #include "EnumIO.h"
// #include "Enums.h"
// #include <iostream>
//
// int
// main()
// {
//     Family family;
//
//     family = Family::SLOW;
//     std::cout << family << std::endl;
//
//     std::cin >> family;
//     std::cout << family << std::endl;
//
//     return 0;
// }
//
// For the input
//
//     fAsT
//
// the output is
//
//     SLOW
//     FAST

template <typename T>
class EnumIO
{
public:
    static const std::vector<std::string>& enum_names();
};

template <typename T,
        typename std::enable_if<std::is_enum<T>::value>::type* = nullptr>
std::ostream&
operator<<(std::ostream& os, const T& t)
{
    os << EnumIO<T>::enum_names().at(static_cast<int>(t));

    return os;
}

static std::string
toUpper(const std::string& input)
{
    std::string copy(input);
    std::transform(copy.cbegin(), copy.cend(), copy.begin(),
            [](const unsigned char i) { return std::toupper(i); });

    return copy;
}

template <typename T,
        typename std::enable_if<std::is_enum<T>::value>::type* = nullptr>
std::istream&
operator>>(std::istream& is, T& t)
{
    std::string input;
    is >> input;
    if (is.fail())
        return is;
    input = toUpper(input);

    // check for a match with a name
    int i = 0;
    for (auto name : EnumIO<T>::enum_names()) {
        if (toUpper(name) == input) {
            // Here we assume that the integer representation of
            // the enum class is the default.  If the enum class
            // members are assigned other integers, this code
            // must be extended by consulting a vector containing
            // the assigned integers.
            t = static_cast<T>(i);

            return is;
        }
        ++i;
    }

    // check for a match with an integer
    int n = static_cast<int>(EnumIO<T>::enum_names().size());
    std::istringstream iss(input);
    int value;
    iss >> value;
    if (not iss.fail() && 0 <= value && value < n) {
        t = static_cast<T>(value); // See the comment above.
        return is;
    }

    is.setstate(std::ios::failbit);

    return is;
}

#endif /* ENUMIO_H_ */


评论


\ $ \ begingroup \ $
谢谢Loki的回复。但是,我需要更多信息:(a)“通过枚举对[my]函数和type_map进行模板化”需要做些什么; (b)从枚举转换为字符串时,我不进行搜索(代码就像您的代码一样)。
\ $ \ endgroup \ $
–Gidfiddle
17年5月13日在3:30



\ $ \ begingroup \ $
这样读起来不是很繁重吗?
\ $ \ endgroup \ $
–难以置信
17年5月13日下午4:36

#4 楼

如果枚举较大,则可能会发现数组中的线性搜索效率不高。还存在意外遗漏一个或多个映射的真实风险。

我通过将enum→string转换编写为switch(带有编译器警告以表示未填写case)来解决了另一种方式,然后在首先需要它时生成一个字符串→枚举std::map

std::string to_string(some_class::enum_type e) {
    switch (e) {
    // you might want to use a macro to get matching labels and strings
    case some_class::enum_type::none: return "none";
    case some_class::enum_type::type1: return "type1";
    case some_class::enum_type::type2: return "type2";
    // N.B. no 'default', or GCC won't warn about missing case
    }
    // invalid value
    return {};
}

some_class::enum_type from_string(const std::string& s) {
    static auto const m = invert(some_class::enum_type::none,
                                 some_class::enum_type::type2,
                                 to_string);
    auto it = m.find(s);
    return it == m.end() ? some_class::enum_type::none : *it;
}

template<typename T, typename R>
std::map<R,T> invert(T first, T last, R(*forward_func)(T))
{
    if (first > last) std::swap(first, last);

    std::map<R,T> m;
    for (int i = first;  i <= last;  ++i)  {
        T t = T(i);
        R r = to_string(t);
        m[r] = t;
        // Or: if (!m.insert_or_assign[t].second)
        //         log_warning(m[r] and t both map to r);
    };
    return m;
}


要使from_string()成为模板,您需要某种enum_traits<T>指定“第一个” '和'last'值,如果找不到该字符串,则返回默认值(检查每个枚举是否映射回自身时,单元测试可以使用这些限制)。

您可能还需要帮助编译器选择正确的to_string()重载;或者,您应该可以将其内联到invert()中。就我而言,根据上下文,我继承的一些枚举有多个到/从字符串的映射,因此对它们全部调用to_string对我来说不是一个选择。

#5 楼

一个好的开始。但是您需要通过枚举来对功能和type_map进行模板化,以使设计可扩展。这是一个简单的更改,因此我将不再关注。

关于唯一的另一件事是,您在进行任一方向的转换时都进行搜索。通过选择合适的容器来保存信息,您可以在一个方向上进行快速查找(尽管在没有两个容器的情况下不能比普通容器更快地进行查找)。

否则我很喜欢它。

这不是我会选择的样式-请参阅我的替代答案。

#6 楼

如果我自定义枚举,例如hi = 10,low = 20,则会给我en错误..

结束于退出代码139的过程(被信号11:SIGSEGV中断)

// Define Enum Like this
enum X {Hi = 10, Lo = 20};
// Then you just need to define their string values.
template<> char const* enumStrings<X>::data[] = {"Hi", "Lo"};


评论


\ $ \ begingroup \ $
这似乎不是对原始问题的代码审查。答案不检查原始代码的任何部分。
\ $ \ endgroup \ $
–pacmaninbw
20-2-8在14:49