web-dev-qa-db-ja.com

std :: mapの初期化にc ++ 11 constexprを使用

キーが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をコンパイルしますが、結果のバイナリにはキータイプの文字列が含まれます。

C String Literals

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
8
muffel

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"}
};
1
Vittorio Romeo

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より早く消費されます。

1
zertyz

GCC 7.2、clang 5.0、またはMSVC 17では--std=c++11 -O2を使用して問題を再現できません。

[〜#〜] demo [〜#〜]

-g)にデバッグシンボルを使用してビルドしていますか?それはあなたが見ているものかもしれません。

1
rustyx
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"}
};

タイプ固有のバージョンがなくても、ほとんどのプリミティブタイプ定数で動作します。