This is an archive of the discontinued LLVM Phabricator instance.

is_integral_or_enum ❥ enum class ⇒ hashable enum class
ClosedPublic

Authored by jfb on Apr 9 2016, 12:48 PM.

Details

Summary

As discussed in D18775 making AtomicOrdering an enum class makes it non-hashable, which shouldn't be the case. Hashing.h defines hash_value for all is_integral_or_enum, but type_traits.h's definition of is_integral_or_enum only checks for *inplicit* conversion to integral types which leaves enum classes out and is very confusing because is_enum is true for enum classes.

This patch:

  • Adds a check for is_enum when determining is_integral_or_enum.
  • Explicitly converts the value parameter in hash_value to handle enum class hashing.

Note that the warning at the top of Hashing.h still applies: each execution of the program has a high probability of producing a different hash_code for a given input. Thus their values are not stable to save or persist, and should only be used during the execution for the construction of hashing datastructures.

Diff Detail

Repository
rL LLVM

Event Timeline

jfb updated this revision to Diff 53147.Apr 9 2016, 12:48 PM
jfb retitled this revision from to is_integral_or_enum ❥ enum class ⇒ hashable enum class.
jfb updated this object.
jfb added reviewers: dberlin, chandlerc.
jfb added a subscriber: llvm-commits.
dberlin edited edge metadata.Apr 9 2016, 12:56 PM
dberlin added a subscriber: dberlin.

This looks better than the hackery i came up with, so LGTM :)

One nit in case we want to care:

+ return ::llvm::hashing::detail::hash_integer_value((uint64_t)value);

So, you are guaranteed this value is either an enum, *or* convertible to
unsigned long long.

But what is the enum is not an unsigned type?

IE will this not still trigger on:

enum class e2: int {};

?

I know these are all just hash values, so who cares, but in practice, you
may just want to use an explicit cast to std::underlying_type instead of
uint64_t.
or something. Again, my C++ knowledge mostly died after C++98, so not sure
what the "right" answer is here.

jfb updated this revision to Diff 53150.Apr 9 2016, 1:08 PM
jfb edited edge metadata.
  • Use underlying_type
jfb added a comment.Apr 9 2016, 1:08 PM

This looks better than the hackery i came up with, so LGTM :)

One nit in case we want to care:

+ return ::llvm::hashing::detail::hash_integer_value((uint64_t)value);

So, you are guaranteed this value is either an enum, *or* convertible to
unsigned long long.

But what is the enum is not an unsigned type?

IE will this not still trigger on:

enum class e2: int {};

?

I know these are all just hash values, so who cares, but in practice, you
may just want to use an explicit cast to std::underlying_type instead of
uint64_t.
or something. Again, my C++ knowledge mostly died after C++98, so not sure
what the "right" answer is here.

I was being a bit lazy because the signature is hash_code hash_integer_value(uint64_t value), but you're right using underlying_type is probably better if hash_integer_value's input type were to change.

Fixed that.

This revision was automatically updated to reflect the committed changes.
jfb added a comment.Apr 9 2016, 1:31 PM
In D18938#396373, @jfb wrote:

This looks better than the hackery i came up with, so LGTM :)

One nit in case we want to care:

+ return ::llvm::hashing::detail::hash_integer_value((uint64_t)value);

So, you are guaranteed this value is either an enum, *or* convertible to
unsigned long long.

But what is the enum is not an unsigned type?

IE will this not still trigger on:

enum class e2: int {};

?

I know these are all just hash values, so who cares, but in practice, you
may just want to use an explicit cast to std::underlying_type instead of
uint64_t.
or something. Again, my C++ knowledge mostly died after C++98, so not sure
what the "right" answer is here.

I was being a bit lazy because the signature is hash_code hash_integer_value(uint64_t value), but you're right using underlying_type is probably better if hash_integer_value's input type were to change.

Fixed that.

Well that was slightly broken, but not locally. I did a quick fix for the bots in http://reviews.llvm.org/rL265880, will ponder a yet-more-correct one.

jfb added a comment.Apr 9 2016, 2:11 PM
In D18938#396391, @jfb wrote:
In D18938#396373, @jfb wrote:

I know these are all just hash values, so who cares, but in practice, you
may just want to use an explicit cast to std::underlying_type instead of
uint64_t.

I was being a bit lazy because the signature is hash_code hash_integer_value(uint64_t value), but you're right using underlying_type is probably better if hash_integer_value's input type were to change.

Fixed that.

Well that was slightly broken, but not locally. I did a quick fix for the bots in http://reviews.llvm.org/rL265880, will ponder a yet-more-correct one.

OK I changed my mind on this and I think uint64_t is the right thing instead of template hackery to get the most restrictive type. My reasoning is based on the comment for hash_value:

/// Note that this function is intended to compute the same hash_code for
/// a particular value without regard to the pre-promotion type. This is in
/// contrast to hash_combine which may produce different hash_codes for
/// differing argument types even if they would implicit promote to a common
/// type without changing the value.

Getting underlying_type gets us the pre-promotion value and then implicitly converts it to uint64_t, whereas casting to uint64_t directly gets us the widest integral type supported here, so it seems to be the right type and follows the implicit conversion that was occurring previously.

WDYT?

SGTM.
I suggest we do it the way you suggest unless someone wants to opine in
post-commit review.