I am trying to use a custom hash struct for class A
which is a key type in an std::unordered_map
which is one of std::variant
alternatives in class B
.
Here is a simplified version of my code where the error can be reproduced:
#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;
}
I am compiling with GCC 9.4.0 with command: g++ -std=c++17 -Wall -Wextra -pedantic -O0 main.cpp
Compilation is failing with the following message:
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 {
| ^
Why am I getting messages about copy consturctors here? It is my understanding that the compiler will generate default copy and move constructors if I do not explicitly make my own
Before using this implementation, instead of std::unordered_map
I was using an std::map
with the appropriate operator overloads in class A
and everything worked fine
The mention of a copy constructor is a red herring. The error occurs while generating one of the copy constructors, but the error has nothing to do with copy constructors:
error: ‘std::pair<_T1, _T2>::second’ has incomplete type
The error is an incomplete type. Your B
contains a std::unordered_map
that itself contains B
. The definition of B
is not complete until its closing brace; as such B
, when the unordered map is declared is incomplete, it's a forward declaration.
There were variations in various revisions of the C++ standard regarding which containers can be defined with an incomplete class for the containers' values, and it only needs to be defined when it's actually used. That's a separate issue, but insofar as your question as to what a copy constructor has to do with any of this, the answer is that it doesn't.