使用 c 11 constexpr 进行 std::map 初始化

新手上路,请多包涵

我想初始化一个 std::map ,键是 constexpr 。考虑以下 C++11 MWE:

 #include <map>
using std::map;

constexpr unsigned int str2int(const char* str, const int h = 0) {
    return !str[h] ? 5381 : (str2int(str, h + 1) * 33) ^ str[h];
}

const map<unsigned int, const char*> values = {
    {str2int("foo"), "bar"},
    {str2int("hello"), "world"}
};

int main() { return 0; }

当代码编译最近的 clang 和 gcc 时,生成的二进制文件将包含键类型的字符串:

C 字符串文字

为什么密钥包含在二进制文件中,即使它们被用作 constexpr 的?有什么办法可以解决这种行为?

当然,地图初始化将在运行时发生。但是不应该在编译时用 constexpr 替换二进制文件中的值吗?

注意:这当然是一个简化的例子。我知道有不同的 升压 结构可能更适合这个用例。我对 为什么 会发生这种情况特别感兴趣。

[编辑]

无论是否启用优化,都会发生该行为。以下代码编译时 bar 是字符串表中唯一的用户定义字符串:

 #include <map>
#include <iostream>
#include <string>

using namespace std;

constexpr unsigned int str2int(const char* str, const int h = 0) {
  return !str[h] ? 5381 : (str2int(str, h + 1) * 33) ^ str[h];
}

int main() {
  string input;
  while(true) {
    cin >> input;
    switch(str2int(input.c_str())) {
      case str2int("quit"):
      return 0;
      case str2int("foo"):
      cout << "bar" << endl;
    }
  }
}

为了验证结果,我使用了一个小的 shell 脚本

$ for x in "gcc-mp-7" "clang"; do
  $x --version|head -n 1
  $x -lstdc++ -std=c++11 -Ofast constexpr.cpp -o a
  $x -lstdc++ -std=c++1z -Ofast constexpr.cpp -o b
  strings a|grep hello|wc -l
  strings b|grep hello|wc -l
done

gcc-mp-7 (MacPorts gcc7 7.2.0_0) 7.2.0
       1
       0
Apple LLVM version 8.1.0 (clang-802.0.38)
       1
       0

原文由 muffel 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 2k
2 个回答

这个线程并不是很新鲜,但有时仍然需要坚持 c++11:|

如何使用 constexpr 函数来设置键:

 constexpr int makeKey(const char* s) { // c++ refused 'auto' here
  return str2int(s); // using str2int from above
}

const std::map<unsigned int, const char*> values = {
    {k0, "bar"}, // these require another declaration (see above)
    {k1, "world"},
    {makeKey("its"), "me"} // this initialization is 'single source'
};

一旦它们变得更大,“单一来源”密钥就可以简化此类地图的维护……

我的小测试程序

...

int main(int argc, char** argv) {

  for(int i(1);i<argc;++i)  {
    const std::map<unsigned int, const char*>::const_iterator cit(values.find(str2int(argv[i])));
    std::cout << argv[i] << " gets " << (cit==values.cend()?"nothing":cit->second) << std::endl;
  }

  return 0;
}

如果使用 gcc 7.5 编译,则工作正常并且不包含任何关键字符串

--std=c++11 -O0

原文由 stacky67 发布,翻译遵循 CC BY-SA 4.0 许可协议

仅声明为 const 是不够的。字符串包含在二进制文件中,因为:

 const map<unsigned int, const char*> values

是 const,但不是 constexpr。它会在你的程序启动时运行’str2int’,而不是在编译时。作为 const 只会向您保证它不会允许进一步的修改,但不会在编译时间上做出妥协。

它接缝你正在搜索 Serge Sans Paille 的 Frozen constexpr 容器——https: //github.com/serge-sans-paille/frozen

虽然我不知道它是否适用于 C++11,但如果你想要性能提升,绝对值得一试。

您可以创建在编译时进行散列的映射,并为您提供生成完美散列函数的额外好处——允许在 O(1) 时间(恒定时间)内访问所有键。

它确实是 gperf 的一个非常称职的替代品。

目前,Clang 和 GCC 对您在编译时能够处理的密钥数量施加了限制。在我的 1G RAM VPS 上仅使用 clang 生成具有 2048 个键的地图就可以了。 GCC 目前更糟糕,并且会更快地吃掉你所有的 RAM。

原文由 zertyz 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题