c++ 如何以最小的代码量最佳地翻译枚举?

nkcskrwz  于 2023-06-07  发布在  其他
关注(0)|答案(7)|浏览(155)

通常情况下,您有两个等价但编号不同的枚举,并且您需要函数将第一个枚举中的元素转换为第二个枚举中的元素或相反的元素。这通常是非常繁琐的代码,任何时候添加一个新元素,你都必须确保将配对添加到转换和反向转换函数中(这违反了the DRY principle)。
什么是最不容易出错的方法来做这件事,仍然生成有效的代码?我提到了高效的部分,因为如果运行时查找不是问题的话,你可以只做一堆对,然后把它们放在std::maps中。我更喜欢像手动编写从一个枚举值到另一个枚举值的大型switch语句一样高性能的东西。
通过一些boost预处理器魔术或一些模板技巧,我相信你可以想出一些东西,你可以写一个对列表,然后生成转换和反向转换函数,但我不确定更喜欢哪种方法或为什么。这两种方法都有编译速度慢和难以诊断编译错误的名声。或者完全是其他的方法?

g9icjywg

g9icjywg1#

就像尼尔说的,我自己从来没有面对过这个问题。我曾经遇到过代码集的问题(即从字符串Map到枚举,然后返回)。
我的第一React是表皮:干。
如您所述,维护两个枚举和转换需要时间,因此最好重构并仅使用一个枚举。
我的第二个React是尝试删除枚举。我不喜欢枚举。也许我会在即将到来的C++0x中欣赏它们,但在我看来,它们带来的麻烦比它们的价值更大。我更喜欢智能对象,带有类别等...
不过,我想这个问题本身也很有趣。所以如果你喜欢处理这种混乱的局面,我不妨试着减轻你的负担。
模板在这里做不了什么。我已经用它们对枚举进行范围检查,并进行字符串转换(来回)和迭代,但这就是它们所能做的。然而,正如你所怀疑的,这是可能的一些“微妙”的应用程序的Boost. Preprocessor。
你想写什么:

DEFINE_CORRESPONDING_ENUMS(Server, Client,
  ((Server1, 1, Client1, 6))
  ((Server2, 2, Client2, 3))
  ((Common1, 4, Common1, 4))
  ((Common2, 5, Common2, 5))
  ((Server3, 7, Client3, 1))
);

我们希望它能产生:

struct Server
{
  enum type { Server1 = 1, Server2 = 2, Common1 = 4, Common2 = 5, Server3 = 7 };
};

struct Client
{
  enum type { Client1 = 6, Client2 = 3, Common1 = 4, Common2 = 5, Client3 = 1 };
};

Server::type ServerFromClient(Client::type c)
{
  switch(c)
  {
  case Client1: return Server1;
  //...
  default: abort();
  }
}

Client::type ClientFromServer(Server::type s)
{
  //...
}

好消息是,这是可能的。我甚至可以做到这一点,虽然我可能会让你在它的工作一点;)
以下是一些解释:

  • 宏的第三个元素是序列。序列的大小是无限的。
  • 序列的每个元素是一个4元组。您需要提前知道它的大小,因此Common的重复。如果使用序列,则可以处理可变数量的元素(例如,避免重复常见的...),但这会使事情变得更加复杂
  • 您需要查看BOOST_PP_SEQ_FOREACH,它将是这里的基本操作。
  • 不要忘记使用BOOST_PP_CAT来处理令牌的连接。
  • gcc上,-E选项产生预处理器输出,它可能会很方便...
  • 别忘了注解和如何在文件中使用,否则你的同事会讨厌你的
flvlnr44

flvlnr442#

你在找这样的东西吗?还没测试过,但应该能用。
(关于过早优化和需要分析的标准警告适用;一个std::map查找可能没有那么糟糕,一个巨大的开关表可能没有那么好。)
enums-impl.h:

// No include guard.
DEFINE_ENUM_PAIR(EGA_BRIGHT_RED, 12, HTML_RED, 0xff0000)
DEFINE_ENUM_PAIR(EGA_BRIGHT_BLUE, 9, HTML_BLUE, 0x0000ff)
DEFINE_ENUM_PAIR(EGA_BRIGHT_GREEN, 10, HTML_GREEN, 0x00ff00)
DEFINE_ENUM_PAIR(EGA_BLACK, 0, HTML_BLACK, 0x000000)

enums.cpp:

enum EgaColorType {
#define DEFINE_ENUM_PAIR(name1, value1, name2, value2) name1 = value1,
#include "enums-impl.h"
#undef DEFINE_ENUM_PAIR
};

enum HtmlColorType {
#define DEFINE_ENUM_PAIR(name1, value1, name2, value2) name2 = value2,
#include "enums-impl.h"
#undef DEFINE_ENUM_PAIR 
};

HtmlColorType ConvertEgaToHtml(EgaColorType c) {
switch (c) {
#define DEFINE_ENUM_PAIR(name1, value1, name2, value2) case name1: return name2;
#include "enums-impl.h"
#undef DEFINE_ENUM_PAIR
default: assert(false);
}

EgaColorType ConvertHtmlToEga(HtmlColorType c) {
switch (c) {
#define DEFINE_ENUM_PAIR(name1, value1, name2, value2) case name2: return name1;
#include "enums-impl.h"
#undef DEFINE_ENUM_PAIR
default: assert(false);
}
ifmq2ha2

ifmq2ha23#

为什么查找表不起作用?为什么要使用这个巨大的switch语句呢??

xu3bshqb

xu3bshqb4#

如果枚举范围相对密集(而不是用作位图指示符),则可以使用数组进行Map。你让编译器计算出数组的长度,然后你可以Assert长度是否不是你想要的。你甚至可以使用static_assert,我不确定。由于您使用的是数组,因此转换应该是常量时间,如果编译器不在内部生成跳转表,转换可能比开关更好。请注意,此代码完全未经测试。

enum A
{
    MIN_A = 1,
    A_ATT_1 = 1,
    A_ATT_2 = 2,
    A_ATT_3 = 3,
    LAST_A
};

enum B
{
    MIN_B = 2
    B_ATT_2 = 2,
    B_ATT_1 = 4,
    B_ATT_3 = 5,
    LAST_B
};

B A_to_B[] =
{
    B_ATT_1,
    B_ATT_2,
    B_ATT_3
};

// Somewhere that will always run, as desired:
assert(LAST_A - MIN_A == sizeof(A_to_B) / sizeof(A_to_B[0]);

B from_A(A in)
{
    B ret = A_to_B[in - MIN_A];
    assert(ret != LAST_B);
    return ret;
}

A B_to_A[] =
{
    A_ATT_2,
    LAST_A,
    A_ATT_1,
    A_ATT_3
};

// Somewhere that will always run, as desired:
assert(LAST_B - MIN_B == sizeof(B_to_A) / sizeof(B_to_A[0]);

A from_B(B in)
{
    A ret = B_to_A[in - MIN_B];
    assert(ret != LAST_A);
    return ret;
}
6gpjuf90

6gpjuf905#

好吧,你总是可以尝试创建一个函数(在数学函数的意义上,而不是编程函数),将一个枚举的数字转换为另一个枚举的数字。但是,每次添加元素时,此函数都会更改。

tquggr8v

tquggr8v6#

考虑不要使用两个枚举。
这两者之间没有太大的区别:

enum FirstSet { A=4, B=6, C=8, D=5 };
enum SecondSet { E=2, F=5, G=5, H=1 };

还有这个

enum OneEnum { A, B, C, D };
enum TwoEnum { E, F, G, H };
int FirstSet[] = { 4, 6, 8, 5 };
int SecondSet[] = { 2, 5, 5, 1 };

需要改变的访问次数可能会令人望而却步,但这比每次想要转换时的O(n)查找要好一些。

w3nuxt5m

w3nuxt5m7#

老问题,但可以用现代C解决。
创建一个enum-pair s的constexpr std::array。然后在这些数组中搜索(C
20:find_if,C++17手动循环)。这也是constexpr

#include <array>
#include <algorithm>
#include <cassert>

enum class E1
{
    E1V1,
    E1V2,
    E1V3,
    E1V4,
    E1V5,
    E1V6,
    E1V7,
    E1V8,
    E1V9,
    E1V0,
};
enum class E2
{
    E2V1 = 77,
    E2V2,
    E2V3,
    E2V4,
    E2V5,
    E2V6 = 1022,
    E2V7,
    E2V8,
    E2V9,
    E2V0,
};

static constexpr std::array conv_table
{
    std::pair(E1::E1V1, E2::E2V1),
    std::pair(E1::E1V2, E2::E2V2),
    std::pair(E1::E1V3, E2::E2V3),
    std::pair(E1::E1V4, E2::E2V4),
    std::pair(E1::E1V5, E2::E2V5),
    std::pair(E1::E1V6, E2::E2V6),
    std::pair(E1::E1V7, E2::E2V7),
    std::pair(E1::E1V8, E2::E2V8),
    std::pair(E1::E1V9, E2::E2V9),
    std::pair(E1::E1V0, E2::E2V0)
};

constexpr E1 convertC(E2 e) 
{
    const auto it = std::find_if(
        conv_table.begin(), conv_table.end(), [e](auto pair){return std::get<1>(pair) == e;});
    return std::get<0>(*it);
}

E1 convert(E2 e) 
{
    return convertC(e);
}

constexpr E2 convertC(E1 e)
{
    const auto it = std::find_if(
        conv_table.begin(), conv_table.end(), [e](auto pair){return std::get<0>(pair) == e;});
    return std::get<1>(*it);
}

E2 convert(E1 e)
{
    return convertC(e);
}

int main() {
    assert(E1::E1V1 == convertC(E2::E2V1));
    assert(E1::E1V9 == convert(E2::E2V9));
    return 0;
}

compiler explorer中。
转换文字(convertC)完全在编译时完成(第一个Assert甚至不生成代码),转换动态值(convert)是一个非常理想的跳转表:

枚举可以是独立的(在其他头中定义),有间隙或负值。使用类枚举。创建阵列时没有重复。

相关问题