yohhoyの日記

技術的メモをしていきたい日記

ヌル終端文字列のstd::hash計算

C++標準ライブラリのハッシュ計算ファンクタstd::hashを用いてヌル終端文字列のハッシュ計算を行う場合、ポインタ型に対するstd::hash<const char*>特殊化ではなくstd::hash<std::string_view>特殊化を用いる。*1

下記コードにあるh0では、“文字列” からではなくアドレス値からハッシュ計算されてしまう。*2

#include <cassert>
#include <functional>
#include <string_view>

int main()
{
  char str[] = "Hello";

  // NG: 意図に反してアドレス値からハッシュ計算
  std::hash<const char*> h0;
  assert( h0(str) != h0("Hello") );
  // 変数strと文字列リテラルは異なるアドレス値を持つため
  // (非常に高確率で)ハッシュ値も不一致となる

  // OK: string_view経由で文字列のハッシュ計算
  std::hash<std::string_view> h1;
  assert( h1(str) == h1("Hello") );
  // 文字列"Hello"から同一ハッシュ値が算出される
}

おまけ:2つの文字列リテラルが異なるアドレス値を持つかは未規定。*3

std::hash<const char*> h0;
const char* str = "Hello";
assert( h0(str) != h0("Hello") );  // ??

関連URL

*1:セマンティクス上は std::hash<std::string> でも期待通り動作するが、std::string 型の一時オブジェクト構築オーバーヘッドが生じる。C++11/14 時点では std::string_view が提供されないため、選択肢は std::hash<std::string> のみ。

*2:低品質なC++標準ライブラリ実装もしくは偶発的なハッシュ衝突によって、 h0(str) == h0("Hello") となる可能性もゼロではない。こんなところで運を使わない(・ε・ )

*3:C++20 5.13.5/p14: "Whether all string-literals are distinct (that is, are stored in non overlapping objects) and whether successive evaluations of a string-literal yield the same or a different object is unspecified."