我想初始化一个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,但生成的二进制文件将包含密钥类型的字符串:
为什么密钥包含在二进制文件中,即使它们被用作constexpr?有什么方法可以解决这种问题?
当然,地图初始化将在运行时进行。但是不应该在编译时用constexpr替换二进制文件中的值吗?
注意:这当然是一个简化的例子。我知道有不同的 boost 结构可能更适合这种用例。我特别感兴趣为什么这种情况正在发生。
[编辑]
无论是否启用优化,都会发生此行为。 以下代码使用 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
答案 0 :(得分:3)
这个帖子不是很新鲜,但有时仍然需要坚持使用 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;
}
工作正常,如果使用
--std=c++11 -O0使用 gcc 7.5 编译,则不包含任何关键字符串
答案 1 :(得分:1)
我无法用g ++(trunk)或clang ++(trunk)重现。我使用了以下标志:-std=c++1z -Ofast
。然后,我使用strings
检查了已编译二进制文件的内容:"foo"
或"hello"
都没有。
您是否已启用优化编译?
无论如何,使用str2int
并不会强制进行编译时评估。为了强制它,你可以这样做:
constexpr auto k0 = str2int("foo");
constexpr auto k1 = str2int("hello");
const map<unsigned int, const char*> values = {
{k0, "bar"},
{k1, "world"}
};
答案 2 :(得分:1)
答案 3 :(得分:0)
template<unsigned int x>
using kuint_t = std::integral_constant<unsigned int, x>;
const map<unsigned int, const char*> values = {
{kuint_t<str2int("foo")>::value, "bar"},
{kuint_t<str2int("hello")>::value, "world"}
};
应该强制编译时评估。
在c++14中,它有点简洁:
template<unsigned int x>
using kuint_t = std::integral_constant<unsigned int, x>;
template<unsigned int x>
kuint_t<x> kuint{};
const map<unsigned int, const char*> values = {
{kuint<str2int("foo")>, "bar"},
{kuint<str2int("hello")>, "world"}
};
并在c++17中:
template<auto x>
using k_t = std::integral_constant<std::decay_t<decltype(x)>, x>;
template<auto x>
k_t<x> k{};
const map<unsigned int, const char*> values = {
{k<str2int("foo")>, "bar"},
{k<str2int("hello")>, "world"}
};
它适用于大多数基本类型常量而没有特定于类型的版本。
答案 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当前对在编译时可以处理的键数施加了限制。仅仅使用clang,用2048键制作地图在我的1G RAM VPS上还可以。目前,GCC甚至更糟,它将更快地消耗掉所有RAM。