Initialization of the copy list versus direct initialization of the temporary list

Given the following structure:

struct ABC { ABC(){cout << "ABC" << endl;} ~ABC() noexcept {cout << "~ABC" << endl;} ABC(ABC const&) {cout << "copy" << endl;} ABC(ABC&&) noexcept {cout << "move" << endl;} ABC& operator=(ABC const&){cout << "copy=" << endl;} ABC& operator=(ABC&&) noexcept {cout << "move=" << endl;} }; 

Conclusion:

 std::pair<std::string, ABC> myPair{{}, {}}; 

is an:

 ABC copy ~ABC ~ABC 

So far, the conclusion:

 std::pair<std::string, ABC> myPair{{}, ABC{}}; 

is an:

 ABC move ~ABC ~ABC 

In an attempt to understand the difference between the two, I think I determined that the first case uses copy list initialization, and the second uses an unnamed temporary direct list initialization (numbers 7 and 2 respectively, here: http://en.cppreference.com/w / cpp / language / list_initialization ).

Searching for Similar Questions I found this: Why is the standard differentiation between direct list initialization and copy list initialization? and this: Initializing the copy list invokes the copy ctor concept? .

The answers to these questions are discussed by the fact that to initialize the list of copies using an explicit constructor will make the code poorly formed. In fact, if I create the default constructor ABC default, my first example will not compile, but that is (possibly) a different matter.

So the question is: why is the temporary copied in the first case, but moved in the second? What prevents it from moving if the list of copies is initialized?

As a note, the following code:

 std::pair<std::string, ABC> myPair = std::make_pair<string, ABC>({}, {}); 

It also leads to a call to the ABC move constructor (and no call to the copy constructor), but various mechanisms can be involved.

You can try to execute the code (using gcc-4.9.2 in C ++ 14 mode) at: https://ideone.com/Kc8xIn

+5
source share
1 answer

In general, bit-init lists, such as {} , are not expressions and are not of type. If you have a function template

 template<typename T> void f(T); 

and call f( {} ) , the type will not be inferred for T , and the type inference will not be executed.

On the other hand, ABC{} is a prvalue expression of type ABC ("explicit type conversion in functional notation"). For a call of type f( ABC{} ) the function template can deduce the type ABC from this expression.


In C ++ 14, as well as in C ++ 11, std::pair has the following [pairs.pair] constructors; T1 and T2 are std::pair class template template parameter names:

 pair(const pair&) = default; pair(pair&&) = default; constexpr pair(); constexpr pair(const T1& x, const T2& y); template<class U, class V> constexpr pair(U&& x, V&& y); template<class U, class V> constexpr pair(const pair<U, V>& p); template<class U, class V> constexpr pair(pair<U, V>&& p); template <class... Args1, class... Args2> pair(piecewise_construct_t, tuple<Args1...>, tuple<Args2...>); 

Note that there is a constructor

 constexpr pair(const T1& x, const T2& y); // (C) 

But not

 constexpr pair(T1&& x, T2&& y); 

instead there is excellent shipping

 template<class U, class V> constexpr pair(U&& x, V&& y); // (P) 

If you try to initialize std::pair with two initializers, in which at least one of them is a list of bit-init, constructor (P) is not viable because it cannot print its template arguments.

(C) is not a constructor template. Its parameters parameters T1 const& and T2 const& are fixed by the parameters of the class template. A reference to a constant type can be initialized from an empty list with binding to init-init. This creates a temporary object bound to the link. Since a type called const, the constructor (C) copies its arguments to the data members of the class.


When you initialize a pair via std::pair<T,U>{ T{}, U{} } , T{} and U{} are prvalue expressions. The constructor template (P) can infer their types and is viable. Creating an instance created after type inference is a better match than the (C) constructor, because (P) will give rvalue-reference parameters and bind the prvalue arguments to them. (C), on the other hand, binds prvalue arguments to lvalue references.


Why then does a live example move the second argument when called through std::pair<T,U>{ {}, U{} } ?

libstdc ++ defines additional constructors. The following is an excerpt from its std::pair implementation from 78536ab78e, omitting function definitions, some comments, and SFINAE. _T1 and _T2 are the parameter names of the template template for the std::pair class.

  _GLIBCXX_CONSTEXPR pair(); _GLIBCXX_CONSTEXPR pair(const _T1& __a, const _T2& __b); // (C) template<class _U1, class _U2> constexpr pair(const pair<_U1, _U2>& __p); constexpr pair(const pair&) = default; constexpr pair(pair&&) = default; // DR 811. template<class _U1> constexpr pair(_U1&& __x, const _T2& __y); // (X) template<class _U2> constexpr pair(const _T1& __x, _U2&& __y); // (E) <===================== template<class _U1, class _U2> constexpr pair(_U1&& __x, _U2&& __y); // (P) template<class _U1, class _U2> constexpr pair(pair<_U1, _U2>&& __p); template<typename... _Args1, typename... _Args2> pair(piecewise_construct_t, tuple<_Args1...>, tuple<_Args2...>); 

Note the constructor template (E): it will copy the first argument and redirect the second one perfectly. For initialization, such as std::pair<T,U>{ {}, U{} } , it is viable because it only needs to infer the type from the second argument. This is also a better match than (C) for the second argument, and therefore a better match overall.

The comment "DR 811" is contained in libstdc ++ sources. It refers to the LWG DR 811 , which adds some SFINAE but does not contain new constructors.

Constructors (E) and (X) are extensions to libstdc ++. I'm not sure if this is compatible.

libC ++, on the other hand, has no additional constructors. For example, std::pair<T,U>{ {}, U{} } it will copy the second argument .

Live demo with both library implementations

+8
source

All Articles