I stumbled across a behaviour of std::make_pair that I do not understand. Given the following code
#include <iostream>
using namespace std;
#include <memory>
#include <utility>
class TestClass
{
public:
TestClass(const std::string& str) : str{std::make_unique<const std::string>(str)} {};
~TestClass() = default; // (1)
private:
std::unique_ptr<const std::string> str{}; // (2)
// const std::unique_ptr<const std::string> str{}; // (3)
};
int main()
{
// is fine all the time
TestClass myTestClass = TestClass("myTestClass"); // (4)
// doesn't work at all
std::make_pair("myTestClassKey", myTestClass); // (5)
// works, but only wit (1) commented out and (2) instead of (3)
std::make_pair("myTestClassKey", TestClass("myTestClass")); // (6)
return 0;
}
Lines (1) to (3) in TestClass
are causing me a headache. While line (4) works regardless of line (1) and (2) OR (3) uncommented, line (5) does not work at all and line (6) only compiles if line (1) is commented out and line (2) is used instead of line (3). The compiler error I receive is always something like:
||=== Build: Debug in test (compiler: GNU GCC Compiler) ===|
/usr/include/c /9/bits/stl_pair.h||In instantiation of ‘constexpr std::pair<typename std::__decay_and_strip<_Tp>::__type, typename std::__decay_and_strip<_T2>::__type> std::make_pair(_T1&&, _T2&&) [with _T1 = const char (&)[15]; _T2 = TestClass; typename std::__decay_and_strip<_T2>::__type = TestClass; typename std::__decay_and_strip<_Tp>::__type = const char*]’:|
/home/johndoe/Coding/test/main.cpp|26|required from here|
/usr/include/c /9/bits/stl_pair.h|529|error: no matching function for call to ‘std::pair<const char*, TestClass>::pair(const char [15], TestClass)’|
/usr/include/c /9/bits/stl_pair.h|436|note: candidate: ‘template<class ... _Args1, long unsigned int ..._Indexes1, class ... _Args2, long unsigned int ..._Indexes2> std::pair<_T1, _T2>::pair(std::tuple<_Args1 ...>&, std::tuple<_Args2 ...>&, std::_Index_tuple<_Indexes1 ...>, std::_Index_tuple<_Indexes2 ...>)’|
/usr/include/c /9/bits/stl_pair.h|436|note: template argument deduction/substitution failed:|
/usr/include/c /9/bits/stl_pair.h|529|note: mismatched types ‘std::tuple<_Tps ...>’ and ‘const char [15]’|
/usr/include/c /9/bits/stl_pair.h|375|note: candidate: ‘template<class ... _Args1, class ... _Args2> std::pair<_T1, _T2>::pair(std::piecewise_construct_t, std::tuple<_Args1 ...>, std::tuple<_Args2 ...>)’|
...
/usr/include/c /9/bits/stl_pair.h|529|note: candidate expects 0 arguments, 2 provided|
||=== Build failed: 9 error(s), 2 warning(s) (0 minute(s), 0 second(s)) ===|
In short, why's that? My only idea so far is that it could be related to move / copy construction of the std::unique_ptr? But why would line (1) cause trouble then?
CodePudding user response:
Lets breakdown what your code does:
~TestClass() = default; // (1)
You are explicitly defining the destructor as a defaulted destructor. This counts as a user-declared destructor.
std::unique_ptr<const std::string> str{}; // (2)
Declares a non-const unique_ptr
field. This field can be mutated and therefore, moved from.
// const std::unique_ptr<const std::string> str{}; // (3)
Declares a constant unique_ptr
field. You will not be able to move out from this field after object initialisation.
TestClass myTestClass = TestClass("myTestClass"); // (4)
Direct-initialises myTestClass
via copy-elision. This means that there is no copy-/move-constructor or assignment happening here. (https://en.cppreference.com/w/cpp/language/copy_elision)
std::make_pair("myTestClassKey", myTestClass); // (5)
Create a std::pair
with a copy of myTestClass
as the second value. TestClass
cannot be copied - no implicit copy constructor is defined because unique_ptr
does not have one either!
std::make_pair("myTestClassKey", TestClass("myTestClass")); // (6)
Initialise a std::pair
with a prvalue ("pure" rvalue), which is the result of the expression TestClass("myTestClass")
. This type of initialisation requires a move-constructor to be available. An implicitly declared move constructor is defined automatically for you, provided that:
- there are no user-declared copy constructors;
- there are no user-declared copy assignment operators;
- there are no user-declared move assignment operators;
- there is no user-declared destructor. (https://en.cppreference.com/w/cpp/language/move_constructor)
The explicitly defaulted destructor (1) is counted as a user-declared destructor, which means that no implicitly declared move constructor is emitted when (1) is uncommented, hence why the compilation error on (6).