【问题标题】:Is a compile-time checked string-to-int map possible?编译时检查的字符串到整数映射是否可能?
【发布时间】:2013-09-01 14:16:36
【问题描述】:

我可能正在努力实现不可能的目标,但 StackExchange 总是让我感到惊讶,所以请尝试一下:

我需要将名称映射到整数。名称(约 2k)是唯一的。该列表不会有任何添加或删除,并且值不会在运行时更改。

将它们实现为const int 变量可以让我在编译时检查存在和类型。 这在代码中也非常清晰和冗长。错误很容易被发现。

将它们实现为std::map<std::string, int> 为我提供了很大的灵活性来构建名称以通过字符串操作进行查找。我可以使用它来将字符串作为参数提供给函数,这些函数可以通过将前/后缀附加到该字符串来查询列表中的多个值。我还可以通过从循环变量创建键名的数字部分来循环多个值。

现在我的问题是:有没有一种方法可以结合这两种优势?缺少编译时检查(尤其是键存在)几乎杀死了我的第二种方法。 (尤其是 std::map 如果密钥不存在会默默地返回 0,这会造成难以找到的错误。)但是循环和前/后缀添加功能非常有用。

我更喜欢不使用任何其他库(如 boost)的解决方案,但请提出建议,因为无论如何我都可以重新实现它们。

关于我如何处理地图的示例:

void init(std::map<std::string, int> &labels)
{        
  labels.insert(std::make_pair("Bob1" , 45 ));
  labels.insert(std::make_pair("Bob2" , 8758 ));
  labels.insert(std::make_pair("Bob3" , 436 ));
  labels.insert(std::make_pair("Alice_first" , 9224 ));
  labels.insert(std::make_pair("Alice_last" , 3510 ));
}

int main() 
{      
  std::map<std::string, int> labels;
  init(labels);

  for (int i=1; i<=3; i++)
  {
    std::stringstream key;
    key << "Bob" << i; 
    doSomething(labels[key.str()]);
  }

  checkName("Alice");
}

void checkName(std::string name)
{
  std::stringstream key1,key2;
  key1 << name << "_first";
  key2 << name << "_last";
  doFirstToLast(labels[key1.str()], labels[key2.str()]);
}

另一个目标是main() 例程中显示的代码尽可能简单和详细。 (需要非程序员理解。)init() 函数将由某些工具代码生成。 doSomething(int) 函数是固定的,但我可以围绕它们编写包装函数。像checkName() 这样的助手可能更复杂,但需要易于调试。

【问题讨论】:

  • 听起来你想在运行时构造字符串,但不知何故在编译时检查了这个?
  • 有一些方法可以将枚举转换为适当的字符串,所以应该是可能的。虽然如果你在运行时构建字符串,很难看出它是如何编译的
  • 如果你想知道值是否在地图中,你不应该使用map[index]!您可以使用map.find(index) 获取对象的迭代器,或者使用map.end()(如果不存在)或map.at(index) 在访问不存在的元素时获取异常。
  • 我认为你可以用很多元编程魔法来做到这一点。关键是很难(但可能)在类型名称中编码字符串。老实说,我不建议这样做,因为编译可能需要数年时间(每个编译器对可变参数模板参数的数量都有最大限制,可能
  • @Chaos_99 有很多方法可以做到这一点。我们需要更多地了解您的代码才能选择最好的。枚举和令牌粘贴将是运行时最快的。

标签: c++ string map compile-time


【解决方案1】:

实现示例的一种方法是使用枚举和令牌粘贴,如下所示

enum {
  Bob1 = 45,
  Bob2 = 8758,
  Bob3 = 436,
  Alice_first = 9224,
  Alice_last = 3510
};

#define LABEL( a, b ) ( a ## b )

int main() 
{      

  doSomething( LABEL(Bob,1) );
  doSomething( LABEL(Bob,2) );
  doSomething( LABEL(Bob,3) );
}


void checkName()
{
  doFirstToLast( LABEL(Alice,_first), LABEL(Alice,_last) );
}

这是否最好取决于名称的来源。

如果您需要支持 for 循环用例,请考虑

int bob[] = { 0, Bob1, Bob2, Bob3 }; // Values from the enum

int main() 
{      
  for( int i = 1; i <= 3; i++ ) {
    doSomething( bob[i] );
  }
}

【讨论】:

  • 尽管使用了预处理器(在调试过程中会出现问题),但这很好地解决了问题,并且生成了非常冗长且易于理解的代码。我需要在真实代码中检查它是否真的勾选了所有框,但我已经喜欢它了。
  • 不能做 for 循环,因为它仍然只在运行时可用。或者有没有用预处理器做for循环的非邪恶技巧?
【解决方案2】:

我不确定我是否了解您的所有要求,但是如果不使用std::map,这样的事情怎么样。 我假设你有三个字符串,“FIRST”、“SECOND”和“THIRD” 想分别映射到 42、17 和 37。

#include <stdio.h>

const int m_FIRST = 0;
const int m_SECOND = 1;
const int m_THIRD = 2;

const int map[] = {42, 17, 37};

#define LOOKUP(s) (map[m_ ## s])

int main ()
{
  printf("%d\n", LOOKUP(FIRST));
  printf("%d\n", LOOKUP(SECOND));
  return 0;
}

缺点是不能将变量字符串与LOOKUP 一起使用。但现在您可以迭代这些值。

【讨论】:

  • 有趣的想法。将循环限制为连续值,但这实际上是可以接受的。不幸的是,它也很难调试(尝试在数组中找到第 1276 个值)。
【解决方案3】:

可能是这样的(未经测试)?

struct Bob {
    static constexpr int values[3] = { 45, 8758, 436 };
};

struct Alice {
    struct first {
        static const int value = 9224;
    };
    struct last {
        static const int value = 3510;
    };
};

template <typename NAME>
void checkName()
{
    doFirstToLast(NAME::first::value, NAME::last::value);
}

...

constexpr int Bob::values[3]; // need a definition in exactly one TU

int main() 
{
    for (int i=1; i<=3; i++)
    {
       doSomething(Bob::values[i]);
    }

    checkName<Alice>();
}

【讨论】:

  • 好主意,但在我的情况下是不可能的。填充列表的代码生成器的复杂性必须大大提高。我可以修改如何输出每一行,但我不能添加复杂的决策。
【解决方案4】:

使用 enum 你有编译时检查,你可以循环它:

How can I iterate over an enum?

【讨论】:

猜你喜欢
  • 2013-04-09
  • 1970-01-01
  • 2015-09-11
  • 1970-01-01
  • 2014-07-29
  • 2011-03-23
  • 1970-01-01
  • 2016-10-07
  • 1970-01-01
相关资源
最近更新 更多