Home > Mobile >  std::pair gives "no matching function call" error in combination with const std:unique_ptr
std::pair gives "no matching function call" error in combination with const std:unique_ptr

Time:10-11

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 ...>’ andconst 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:

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).

  • Related