2543. LWG 2148 (hash support for enum types) seems under-specified

Section: 23.14.15 [unord.hash] Status: Resolved Submitter: Ville Voutilainen Opened: 2015-09-27 Last modified: 2017-02-02

Priority: 2

View other active issues in [unord.hash].

View all other issues in [unord.hash].

View all issues with Resolved status.

Discussion:

The rationale in issue 2148 says:

This proposed resolution doesn't specify anything else about the primary template, allowing implementations to do whatever they want for non-enums: static_assert nicely, explode horribly at compiletime or runtime, etc.

libc++ seems to implement it by defining the primary template and static_asserting is_enum inside it. However, that brings forth a problem; there are reasonable SFINAE uses broken by it:

#include <type_traits>
#include <functional>

class S{}; // No hash specialization

template<class T>
auto f(int) -> decltype(std::hash<T>(), std::true_type());

template<class T>
auto f(...) -> decltype(std::false_type());

static_assert(!decltype(f<S>(0))::value, "");

MSVC doesn't seem to accept that code either.

There is a way to implement LWG 2148 so that hash for enums is supported without breaking that sort of SFINAE uses:

  1. Derive the main hash template from a library-internal uglified-named base template that takes a type and a bool, pass as argument for the base the result of is_enum.

  2. Partially specialize that base template so that the false-case has a suitable set of private special member function declarations so that it's not an aggregate nor usable in almost any expression.

[2015-10, Kona Saturday afternoon]

EricWF to come back with wording; move to Open

[2016-05-08, Eric Fiselier & Ville provide wording]

[2016-05-25, Tim Song comments]

I see two issues with this P/R:

  1. "for which neither the library nor the user provides an explicit specialization" should probably be "for which neither the library nor the user provides an explicit or partial specialization".

  2. Saying that the specialization "is not DefaultConstructible nor MoveAssignable" is not enough to guarantee that common SFINAE uses will work. Both of those requirements have several parts, and it's not too hard to fail only some of them. For instance, not meeting the assignment postcondition breaks MoveAssignable, but is usually not SFINAE-detectible. And for DefaultConstructible, it's easy to write something in a way that breaks T() but not T{} (due to aggregate initialization in the latter case).

[2016-06-14, Daniel comments]

The problematic part of the P/R is that it describes constraints that would be suitable if they were constraints for user-code, but they are not suitable as requirements imposed on implementations to provide certain guarantees for clients of the Library. The guarantees should be written in terms of testable compile-time expressions, e.g. based on negative results of is_default_constructible<hash<X>>::value, std::is_copy_constructible<hash<X>>::value, and possibly also std::is_destructible<hash<X>>::value. How an implementation realizes these negative results shouldn't be specified, though, but the expressions need to be well-formed and well-defined.

[2016-08-03, Ville provides revised wording as response to Daniel's previous comment]

Previous resolution [SUPERSEDED]:

This wording is relative to N4582.

  1. Insert a new paragraph after 23.14.15 [unord.hash]/2

    -2- The template specializations shall meet the requirements of class template hash (20.12.14).

    -?- For any type that is not of integral or enumeration type, or for which neither the library nor the user provides an explicit specialization of the class template hash, the specialization of hash does not meet any of the Hash requirements, and is not DefaultConstructible nor MoveAssignable. [Note: this means that the specialization of hash exists, but any attempts to use it as a Hash will be ill-formed. — end note]

[2016-08 - Chicago]

Thurs AM: Moved to Tentatively Ready

Previous resolution [SUPERSEDED]:

This wording is relative to N4606.

  1. Insert a new paragraph after 23.14.15 [unord.hash]/2

    [Drafting note: I see no reason to specify whether H<T> is destructible. There's no practical use case for which that would need to be covered. libstdc++ makes it so that H<T> is destructible.]

    -2- The template specializations shall meet the requirements of class template hash (20.12.14).

    -?- For any type T that is not of integral or enumeration type, or for which neither the library nor the user provides an explicit or partial specialization of the class template hash, the specialization of hash<T> has the following properties:

    • is_default_constructible_v<hash<T>> is false
    • is_copy_constructible_v<hash<T>> is false
    • is_move_constructible_v<hash<T>> is false
    • is_copy_assignable_v<hash<T>> is false
    • is_move_assignable_v<hash<T>> is false
    • is_callable_v<hash<T>, T&> is false
    • is_callable_v<hash<T>, const T&> is false

    [Note: this means that the specialization of hash exists, but any attempts to use it as a Hash will be ill-formed. — end note]

[2016-08-09 Daniel reopens]

As pointed out by Eric, the usage of is_callable is incorrect. Eric provides new wording.

[2016-09-09 Issues Resolution Telecon]

Move to Tentatively Ready

[2016-11-12, Issaquah]

Resolved by P0513R0

Proposed resolution:

This wording is relative to N4606.

  1. Insert a new paragraph after 23.14.15 [unord.hash]/2

    [Drafting note: I see no reason to specify whether H<T> is destructible. There's no practical use case for which that would need to be covered. libstdc++ makes it so that H<T> is destructible.]

    -2- The template specializations shall meet the requirements of class template hash (20.12.14).

    -?- For any type T that is not of integral or enumeration type, or for which neither the library nor the user provides an explicit or partial specialization of the class template hash, the specialization of hash<T> has the following properties:

    • is_default_constructible_v<hash<T>> is false
    • is_copy_constructible_v<hash<T>> is false
    • is_move_constructible_v<hash<T>> is false
    • is_copy_assignable_v<hash<T>> is false
    • is_move_assignable_v<hash<T>> is false
    • hash<T> is not a function object type (23.14 [function.objects])

    [Note: this means that the specialization of hash exists, but any attempts to use it as a Hash will be ill-formed. — end note]