1

Assume my_hasher is a hashing function object. Should the following be correct, according to the Standard?

 my_type k;
 assert(my_hasher{}(k) == my_hasher{}(k));

The cppreference states the above assertion is correct.

However, the Standard (16.5.3.4, [hash.requirements]) only requires that for a specific instance of my_hasher,

the value returned shall depend only on the argument k for the duration of the program

...which seems like it permits 2 different instances of my_hasher to return different hash values for the same operand.

So, is cppreference mistaken or am I missing something?

Igor R.
  • 14,716
  • 2
  • 49
  • 83
  • It will be stable for the _"...duration of the program..."_ but if you re-run the program you may get different values for the hash; ie don't store the hash in a file for later comparison purposes in subsequent runs of the program. See note - "_...[Note 1: Thus all evaluations of the expression h(k) with the same value for k yield the same result for a given execution of the program. — end note]..."_ https://eel.is/c++draft/hash.requirements#tab:cpp17.hash-row-2-column-3-note-1 – Richard Critten Jul 19 '22 at 10:28
  • 3
    As I understand it, the value returned must only depend on `k`, which implies that it cannot depend on `h`. So no matter what object `h` is, `h(k)` must be the same for the same `k`. – Yksisarvinen Jul 19 '22 at 10:29
  • 1
    the title is a little misleading. the hash functor can actually differ while the produced hash can still be the same (not sure it's any useful). – apple apple Jul 19 '22 at 10:42
  • @appleapple just wondered the same, because eg `std::unordered_map` has a constructor taking an instance of the hash. The hash instance could for example count how often it is called or stuff like that – 463035818_is_not_an_ai Jul 19 '22 at 11:00
  • How does that quote from the standard disagree with cppreference's writeup? – Sam Varshavchik Jul 19 '22 at 11:01
  • @Yksisarvinen it looks like the dependency on `h` is implicit and inevitable, because `h` is the function object that computes the hash. :) – Igor R. Jul 19 '22 at 14:48
  • @Sam Varshavchik it introduces a requirement, which is not explicitly stated in the standard. – Igor R. Jul 19 '22 at 14:49

1 Answers1

2

The requirements on specialisations of std::hash can be more specific than the requirements in the concept Hash.

Specifically std::hash is required to be DefaultConstructible, whereas general Hashes aren't.

The requirement you are reading is that two default-constructed std::hash<T> objects compute the same hash, not that any two objects of some Hash type compute the same hash.

Caleth
  • 52,200
  • 2
  • 44
  • 75
  • Thanks for the pointer, I've looked at 20.14.18 [unord.hash], it really requires a specialization of `std::hash` to be `DefaultConstructible`, but again, it only requires that (k1 == k2) ==> (h(k1) == h(k2)). It says nothing about the equivalence of default-constructed instances (which might use e.g. some global seed). – Igor R. Jul 19 '22 at 14:09
  • @IgorR. there's [a general prohibition](https://timsong-cpp.github.io/cppwp/n4659/res.on.data.races) on functions in `std` from accessing global values – Caleth Jul 19 '22 at 14:32
  • IUUC, it only speaks about data races, i.e. a global state may be accessed using a proper synchronization. Or let's say we're in a single-threaded environment. – Igor R. Jul 19 '22 at 14:47
  • "This means, for example, that implementations can't use a static object for internal purposes *without synchronization*" - i.e. it may use it *with* synchronization. – Igor R. Jul 19 '22 at 14:52
  • @IgorR. I think it's a hole in the standard, because it doesn't require `std::hash` be *EqualityComparable*, and require equal hashers to result in equal hashes, thus moving nodes between unordered maps might break – Caleth Jul 19 '22 at 15:03