キーがconstexpr
であるstd :: mapを初期化したい。次の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;
}
}
}
小さなシェルスクリプトを使用して結果を確認するには
$ 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
G ++(トランク)もclang ++(トランク)も再現できません。次のフラグを使用しました:-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"}
};
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で2048個のキーを使用してマップを作成しても問題ありませんRAM VPSはclangでのみ使用できます。GCCは現在さらに悪く、すべてのRAMより早く消費されます。
GCC 7.2、clang 5.0、またはMSVC 17では--std=c++11 -O2
を使用して問題を再現できません。
(-g
)にデバッグシンボルを使用してビルドしていますか?それはあなたが見ているものかもしれません。
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"}
};
タイプ固有のバージョンがなくても、ほとんどのプリミティブタイプ定数で動作します。