添加自定义哈希函数时需要复制构造函数的 C++ 无序映射

C++ unordered map requiring copy constructor when adding my custom hash function

提问人:TheMemeMachine 提问时间:3/26/2023 更新时间:3/26/2023 访问量:219

问:

我正在尝试为类使用自定义哈希结构,该结构是类中的一种键类型,它是类中的替代方案之一。 这是我的代码的简化版本,可以在其中重现错误:Astd::unordered_mapstd::variantB

#include <initializer_list>
#include <string>
#include <unordered_map>
#include <variant>

namespace myNamespace {
    class A;
    struct AHasher;
    class B;

    // ....

    class A {
        public:
            A(const std::string& str);
            friend bool operator==(const A& lhs, const A& rhs);
        public:
            std::string value;
    };

    struct AHasher {
        std::size_t operator()(const A& str) const;
    };

    class B {
        public:
            B();
            B(const std::string& str);
            B(const A& str);
            B(const std::initializer_list<std::pair<const A, B>> list);
        private:
            std::variant<A, std::unordered_map<A, B, AHasher>> value;
    };

}  // namespace myNamespace 

// .......

namespace myNamespace {
    A::A(const std::string& str) : value(str) {}
    bool operator==(const A& lhs, const A& rhs) { 
        return lhs.value == rhs.value;
    }

    std::size_t AHasher::operator()(const A& str) const {
        std::hash<std::string> hasher;
        return hasher(str.value);
    }
   
    B::B() : value(std::unordered_map<A, B, AHasher>{}) {}
    B::B(const std::string& str) : value(A(str)) {}
    B::B(const A& str) : value(str) {}
    B::B(const std::initializer_list<std::pair<const A, B>> list) : value(list) {}

} // namespace myNamespace 


int main() {
    return 0;
}

我正在使用 GCC 9.4.0 进行编译,命令:编译失败并显示以下消息:g++ -std=c++17 -Wall -Wextra -pedantic -O0 main.cpp

In file included from /usr/include/c++/9/bits/stl_algobase.h:64,
                 from /usr/include/c++/9/bits/char_traits.h:39,
                 from /usr/include/c++/9/string:40,
                 from tmp.cpp:53:
/usr/include/c++/9/bits/stl_pair.h: In instantiation of ‘struct std::pair<const myNamespace::A, myNamespace::B>’:
/usr/include/c++/9/ext/aligned_buffer.h:91:28:   required from ‘struct __gnu_cxx::__aligned_buffer<std::pair<const myNamespace::A, myNamespace::B> >’
/usr/include/c++/9/bits/hashtable_policy.h:233:43:   required from ‘struct std::__detail::_Hash_node_value_base<std::pair<const myNamespace::A, myNamespace::B> >’
/usr/include/c++/9/bits/hashtable_policy.h:264:12:   required from ‘struct std::__detail::_Hash_node<std::pair<const myNamespace::A, myNamespace::B>, true>’
/usr/include/c++/9/bits/hashtable_policy.h:2027:13:   required from ‘struct std::__detail::_Hashtable_alloc<std::allocator<std::__detail::_Hash_node<std::pair<const myNamespace::A, myNamespace::B>, true> > >’
/usr/include/c++/9/bits/hashtable.h:173:11:   required from ‘class std::_Hashtable<myNamespace::A, std::pair<const myNamespace::A, myNamespace::B>, std::allocator<std::pair<const myNamespace::A, myNamespace::B> >, std::__detail::_Select1st, std::equal_to<myNamespace::A>, myNamespace::AHasher, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<true, false, true> >’
/usr/include/c++/9/bits/unordered_map.h:105:18:   [ skipping 2 instantiation contexts, use -ftemplate-backtrace-limit=0 to disable ]
/usr/include/c++/9/type_traits:901:12:   required from ‘struct std::__is_copy_constructible_impl<std::unordered_map<myNamespace::A, myNamespace::B, myNamespace::AHasher>, true>’
/usr/include/c++/9/type_traits:907:12:   required from ‘struct std::is_copy_constructible<std::unordered_map<myNamespace::A, myNamespace::B, myNamespace::AHasher> >’
/usr/include/c++/9/type_traits:2918:25:   required from ‘constexpr const bool std::is_copy_constructible_v<std::unordered_map<myNamespace::A, myNamespace::B, myNamespace::AHasher> >’
/usr/include/c++/9/variant:275:5:   required from ‘constexpr const bool std::__detail::__variant::_Traits<myNamespace::A, std::unordered_map<myNamespace::A, myNamespace::B, myNamespace::AHasher, std::equal_to<myNamespace::A>, std::allocator<std::pair<const myNamespace::A, myNamespace::B> > > >::_S_copy_ctor’
/usr/include/c++/9/variant:1228:11:   required from ‘class std::variant<myNamespace::A, std::unordered_map<myNamespace::A, myNamespace::B, myNamespace::AHasher, std::equal_to<myNamespace::A>, std::allocator<std::pair<const myNamespace::A, myNamespace::B> > > >’
tmp.cpp:81:64:   required from here
/usr/include/c++/9/bits/stl_pair.h:215:11: error: ‘std::pair<_T1, _T2>::second’ has incomplete type
  215 |       _T2 second;                /// @c second is a copy of the second object
      |           ^~~~~~
tmp.cpp:74:11: note: forward declaration of ‘class myNamespace::B’
   74 |     class B {
      |           ^

为什么我在这里收到有关文案构造者的消息?我的理解是,如果我不显式地制作自己的构造函数,编译器将生成默认的复制和移动构造函数

在使用此实现之前,我没有在类中使用带有适当运算符的重载,而是一切正常std::unordered_mapstd::mapA

C++ 哈希 C++17 复制构造函数 无序映射

评论

3赞 Richard Critten 3/26/2023
仅供参考,GCC 12.1 中似乎修复了某些问题,这无法在 11.3 中编译 - Live godbolt.org/z/1o9sGf9cK 。在 clang 和 MSVC 中编译正常。
2赞 JaMiT 3/26/2023
使用 gcc 9.3,看起来你可以大大简化你的最小可重现示例: -- 要解析的不相关细节越少,就越容易理解问题。#include <unordered_map> class B { std::unordered_map<int, B> value; }; int main() { return 0; }
0赞 Tony Delroy 4/7/2023
@JaMiT也是一个无关紧要和不必要的细节......return 0;
0赞 JaMiT 4/7/2023
@TonyDelroy 返回 0;也是一个无关紧要和不必要的细节“——没错。这就是为什么我没有(或至少不打算)声称我尽可能地简化了。;)我故意将问题中的函数保留为参考点。因为如果我删除了 return 语句,有人会关注该更改,从而忽略对类定义的更改。希望这看起来令人难以置信,但根据我在这里看到的东西和我自己的运气,我会给它 90+% 的机会。main

答:

5赞 Sam Varshavchik 3/26/2023 #1

提到复制构造函数是一条红鲱鱼。生成其中一个复制构造函数时发生错误,但该错误与复制构造函数无关:

错误:“std::p air<_T1,_T2>::second”类型不完整

错误是不完整的类型。您包含一个本身包含 .的定义直到其右大括号才完成;因此,当声明的无序映射不完整时,它是前向声明。Bstd::unordered_mapBBB

C++ 标准的各种修订版对于哪些容器可以使用容器值的不完整类来定义存在差异,并且只需要在实际使用时定义。这是一个单独的问题,但就您关于复制构造函数与任何这些有什么关系的问题而言,答案是它没有。