Skip to content

Recommended way to hash std::optional? #47

@jan-kelemen

Description

@jan-kelemen

Hi,

As far as I can see, the library doesn't provide native implementation for hashing std::optional. So the recommendation from the documentation would be to declare a tag_invoke overload.

When T is a user defined type that does not fall into one of the above categories, it needs to provide its own hashing support, by defining an appropriate tag_invoke overload.
...
It can be defined as a separate free function in the namespace of X, but the recommended approach is to define it as an inline friend in the definition of X:

However, this is a bit problematic, as the user can't extend the definition of std::optional nor add new functions to the std:: namespace as it's technically UB, although something like this does work:

namespace std {
    template<typename Provider, typename Hash, typename Flavor, class T>
    void tag_invoke(
        boost::hash2::hash_append_tag,
        Provider const& pr,
        Hash& h,
        Flavor const& f,
        std::optional<T> const* v)
    {
        bool const has_value{v->has_value()};
        boost::hash2::hash_append(h, f, has_value);
        if (has_value) {
            boost::hash2::hash_append(h, f, v->value());
        }
    }
}

The alternative that also works is to add this definition to the boost::hash2 namespace, which feels a bit dirty:

namespace boost::hash2 {
    template<typename Provider, typename Hash, typename Flavor, class T>
    void tag_invoke(
        boost::hash2::hash_append_tag,
        Provider const& pr,
        Hash& h,
        Flavor const& f,
        std::optional<T> const* v)
    {
        bool const has_value{v->has_value()};
        boost::hash2::hash_append(h, f, has_value);
        if (has_value) {
            boost::hash2::hash_append(h, f, v->value());
        }
    }
}

The cleanest thing to do, would be to add the definition into some third namespace, like:

namespace my_namespace {
    template<typename Provider, typename Hash, typename Flavor, class T>
    void tag_invoke(
        boost::hash2::hash_append_tag,
        Provider const& pr,
        Hash& h,
        Flavor const& f,
        std::optional<T> const* v)
    {
        bool const has_value{v->has_value()};
        boost::hash2::hash_append(h, f, has_value);
        if (has_value) {
            boost::hash2::hash_append(h, f, v->value());
        }
    }
}

However, in that case, the function isn't visible by the library even if it's added to the overload set with a using statement:

int main()
{
    using my_namespace::tag_invoke;

    std::optional<int> value = 33;

    boost::hash2::fnv1a_32 h;
    boost::hash2::hash_append(h, {}, value);
    auto digest = h.result();
}

Full sample code: https://godbolt.org/z/cjMx7qnnx

Maybe I'm missing something from the docs?

Thanks!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions