c++ 返回类型为`auto`的哈希函数错误:“静态Assert失败:哈希函数必须可使用键类型的参数调用”

u3r8eeie  于 2023-04-01  发布在  其他
关注(0)|答案(2)|浏览(129)

在gcc 12.2中,下面的C++代码给了我一个错误。抱怨的要点是“散列函数必须可以用key类型的参数调用”:

#include <cstddef>
#include <unordered_map>
#include <functional>

struct OUTER {
    class KEY {
    public:
        bool operator==(KEY const&) const;
        struct Hash {
            auto operator()(KEY const&) const {
                // Give the hash for ten -- why not?
                return std::hash<size_t>{}(10);
            };
        };
    };
    std::unordered_map<KEY, int, KEY::Hash> m;
    bool test(KEY const& ident) { return m.end() != m.find(ident); }
};

错误消息为:

In file included from /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/bits/hashtable.h:35,
                 from /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/unordered_map:46,
                 from <source>:2:
/opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/bits/hashtable_policy.h: In instantiation of 'std::__detail::_Hash_code_base<_Key, _Value, _ExtractKey, _Hash, _RangeHash, _Unused, __cache_hash_code>::__hash_code std::__detail::_Hash_code_base<_Key, _Value, _ExtractKey, _Hash, _RangeHash, _Unused, __cache_hash_code>::_M_hash_code(const _Key&) const [with _Key = OUTER::KEY; _Value = std::pair<const OUTER::KEY, int>; _ExtractKey = std::__detail::_Select1st; _Hash = OUTER::KEY::Hash; _RangeHash = std::__detail::_Mod_range_hashing; _Unused = std::__detail::_Default_ranged_hash; bool __cache_hash_code = true; __hash_code = long unsigned int]':
/opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/bits/hashtable.h:1653:46:   required from 'std::_Hashtable<_Key, _Value, _Alloc, _ExtractKey, _Equal, _Hash, _RangeHash, _Unused, _RehashPolicy, _Traits>::iterator std::_Hashtable<_Key, _Value, _Alloc, _ExtractKey, _Equal, _Hash, _RangeHash, _Unused, _RehashPolicy, _Traits>::find(const key_type&) [with _Key = OUTER::KEY; _Value = std::pair<const OUTER::KEY, int>; _Alloc = std::allocator<std::pair<const OUTER::KEY, int> >; _ExtractKey = std::__detail::_Select1st; _Equal = std::equal_to<OUTER::KEY>; _Hash = OUTER::KEY::Hash; _RangeHash = std::__detail::_Mod_range_hashing; _Unused = std::__detail::_Default_ranged_hash; _RehashPolicy = std::__detail::_Prime_rehash_policy; _Traits = std::__detail::_Hashtable_traits<true, false, true>; iterator = std::__detail::_Insert_base<OUTER::KEY, std::pair<const OUTER::KEY, int>, std::allocator<std::pair<const OUTER::KEY, int> >, std::__detail::_Select1st, std::equal_to<OUTER::KEY>, OUTER::KEY::Hash, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true> >::iterator; key_type = OUTER::KEY]'
/opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/bits/unordered_map.h:869:25:   required from 'std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::iterator std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::find(const key_type&) [with _Key = OUTER::KEY; _Tp = int; _Hash = OUTER::KEY::Hash; _Pred = std::equal_to<OUTER::KEY>; _Alloc = std::allocator<std::pair<const OUTER::KEY, int> >; iterator = std::__detail::_Insert_base<OUTER::KEY, std::pair<const OUTER::KEY, int>, std::allocator<std::pair<const OUTER::KEY, int> >, std::__detail::_Select1st, std::equal_to<OUTER::KEY>, OUTER::KEY::Hash, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true> >::iterator; key_type = OUTER::KEY]'
<source>:17:59:   required from here
/opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/bits/hashtable_policy.h:1268:23: error: static assertion failed: hash function must be invocable with an argument of key type
 1268 |         static_assert(__is_invocable<const _Hash&, const _Key&>{},
      |                       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/bits/hashtable_policy.h:1268:23: note: 'std::__is_invocable<const OUTER::KEY::Hash&, const OUTER::KEY&>()' evaluates to false
Compiler returned: 1

既然OUTER::KEY::Hash::operator()采用KEY const&作为其参数类型,那么我应该是好的吧?
我希望上面的代码能够工作,因为我的哈希函数确实接受键类型作为参数。
事实上,我收到了一个错误!

jmo0nnb3

jmo0nnb31#

OUTER::KEY::Hash::operator()声明一个显式返回类型似乎可以解决这个问题。

struct Hash {
            size_t operator()(KEY const&) const {
                // Give the hash for zero.
                return std::hash<size_t>{}(10);
            };
        };

std::hash对象通常返回size_t,因此这是这里返回类型的一个很好的候选。
不过,我不知道错误的确切原因,因为如果删除class OUTER,非常相似的代码似乎可以编译。

jum4pzuy

jum4pzuy2#

看起来这与 C++ DR 1890有关。

struct A {
  struct B {
    auto foo() { return 0; }
  };
  decltype(B().foo()) x;
};

尽管C++标准中没有明确指出这段代码是不正确的,但g++, clang++, and MSVC all reject it
您的示例与此类似,示例化std::unordered_map模板专门化涉及到检查键类型是否可以传递给哈希类型。也就是说,decltype(std::declval<OUTER::KEY::Hash const&>()(std::declval<OUTER::KEY const&>()))是有效类型。
我认为下一个例子说明了为什么这类事情与一个没有明确答案的更普遍的问题有关:

int T() { return 1; }
struct A {
  struct B {
    static auto foo() { return T(); }
  };
  using T = double;
  using U = decltype(B::foo());
};

在成员函数体中,封闭类被认为是完整的。(它是double),而不是函数::T,这只有在编译器在处理foo的主体之前处理using T时才会发生,但处理using U只能在处理foo的主体之后才能工作。如果foo涉及到U这个名字,事情就更乱了。

相关问题