简体   繁体   中英

Clang only: A pair<path, path> can be emplaced into a vector; so can a pair<unique_ptr, unique_ptr>; but NOT pair<path, unique_ptr>: Why?

I have the following three snippets of code to demonstrate an easily reproducible issue.

using namespace boost::filesystem;
using namespace std;

int main()
{

    path dummy_path;

    // Snippet 1
    // Two paths
    // Succeeds
    //
    // vector<pair<path, path>> myvec;
    // myvec.emplace_back(dummy_path, dummy_path);

    // Snippet 2
    // Two unique_ptr's
    // Succeeds
    //
    // vector<pair<unique_ptr<int>, unique_ptr<int>>> myvec;
    // myvec.emplace_back(unique_ptr<int>(new int(13)), unique_ptr<int>(new int(12)));

    // Snippet 3
    // A path and a unique_ptr.
    //
    // **FAILS** on Clang, succeeds in Visual Studio
    //
    vector<pair<path, unique_ptr<int>>> myvec;
    myvec.emplace_back(dummy_path, unique_ptr<int>(new int(12)));

}

Here is the compiler error on Clang:

error: call to implicitly-deleted copy constructor of 'std::__1::unique_ptr<int, std::__1::default_delete<int> >' (in reference to the second member of the pair, the unique_ptr , obviously).

It seems that for some reason, the failing case indicated is causing the copy constructor of the pair, rather than the move constructor, to be called.

This is Clang 5.0.2 on OS X 10.8.5. (And VS 11.0.60610.01 Update 3 on Windows 7 64-bit.)

In my actual application, the data types are more complex but the error boils down to the one described in this question.

My question is twofold: Why does the case indicated fail on Clang even though the other two cases, which cover both data types, succeed?

Perhaps more importantly, however: What can I do to work around this problem? Because my actual application is more complex, I do not have the option of not performing the emplace (or something equivalent) of the given pair into the vector - but if there's any other way I can get past this Clang issue to get that pair into that vector, I would be very happy.

The problem comes from the libc++ Standard Library, since it has much less pair constructors within.

Ie libstd++ has following constructor:

template<class _U2, class = typename
       enable_if<is_convertible<_U2, _T2>::value>::type>
constexpr pair(const _T1& __x, _U2&& __y)
: first(__x), second(std::forward<_U2>(__y)) { }

Which allows to compile your sample on Linux (with clang++). But libc++ has only:

pair(const pair&) = default;
pair(pair&&) = default;
constexpr pair();
pair(const T1& x, const T2& y);                          // constexpr in C++14
template <class U, class V> pair(U&& x, V&& y);          // constexpr in C++14
template <class U, class V> pair(const pair<U, V>& p);   // constexpr in C++14
template <class U, class V> pair(pair<U, V>&& p);        // constexpr in C++14
template <class... Args1, class... Args2>
    pair(piecewise_construct_t, tuple<Args1...> first_args,
         tuple<Args2...> second_args);

I guess, that the pair(const T1& x, const T2& y); is applied due to the first argument being non-rvalue reference.


The "hardcore" solution is to place this constructor by hand - inside the system library. The pair is defined in utility file. If you want to be able to compile your code elsewhere, you may bundle the modified libc++ into your project - it's not a big deal, really.

This is a bug in libc++, sorry. It has been fixed on tip-of-trunk. I believe you can work around it by adding the following to your compile command:

-D_LIBCPP_TRIVIAL_PAIR_COPY_CTOR

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM