Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

copy list initialization vs direct list initialization of temporary

Given the following struct:

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;}
};

The output of:

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

is:

ABC
copy
~ABC
~ABC

While the output of:

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

is:

ABC
move
~ABC
~ABC

In attempting to understand the difference between the two I think I have identified that the first case is using copy-list-initialization, while the second one uses direct-list-initialization of an unnamed temporary (numbers 7 and 2, respectively, in here: http://en.cppreference.com/w/cpp/language/list_initialization).

Searching for similar questions I've found this: Why does the standard differentiate between direct-list-initialization and copy-list-initialization? and this: Does copy list initialization invoke copy ctor conceptually?.

The answers in those questions discuss the fact that for copy-list-initialization, the use of an explicit constructor would render the code ill-formed. In fact, if I make ABC's default constructor explicit, my first example won't compile but that is (perhaps) 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 being moved in the case of copy-list-initialization?

As a note, the following code:

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

Also results in a call to ABC's move constructor (and no copy constructor call), but different mechanisms may be involved.

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

like image 967
Hugo Avatar asked May 05 '15 14:05

Hugo


People also ask

What is copy list initialization?

In copy-list-initialization, if an explicit constructor is chosen, the initialization is ill-formed. [ Note: This differs from other situations (13.3.1.3, 13.3.1.4), where only converting constructors are considered for copy initialization.

What is direct initialization?

Direct Initialization or Assignment Operator (Syntax) This assigns the value of one object to another object both of which are already exists. Copy initialization is used when a new object is created with some existing object. This is used when we want to assign existing object to new object.

What is the difference between copy list initialization and direct list initialization?

Direct-initialization is more permissive than copy-initialization: copy-initialization only considers non-explicit constructors and non-explicit user-defined conversion functions, while direct-initialization considers all constructors and all user-defined conversion functions.

When must you use an Initialiser list?

Initializer List is used in initializing the data members of a class. The list of members to be initialized is indicated with constructor as a comma-separated list followed by a colon. Following is an example that uses the initializer list to initialize x and y of Point class.


1 Answers

In general, braced-init-lists like {} are not expressions and do not have a type. If you have a function template

template<typename T> void f(T);

and call f( {} ), no type will be deduced for T, and type deduction will fail.

On the other hand, ABC{} is a prvalue expression of type ABC (an "explicit type conversion in functional notation"). For a call like 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 constructors [pairs.pair]; T1 and T2 are the names of the template parameter of the std::pair class template:

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 no

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

instead, there is a perfectly forwarding

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

If you try to initialize a std::pair with two initializers where at least one of them is a braced-init-list, the constructor (P) is not viable since it cannot deduce its template arguments.

(C) is not a constructor template. Its parameter types T1 const& and T2 const& are fixed by the class template parameters. A reference to a constant type can be initialized from an empty braced-init-list. This creates a temporary object that is bound to the reference. As the type referred to is const, the (C) constructor will copy its arguments into the class' data members.


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


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

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

  _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 (E) constructor template: It will copy the first argument and perfectly forward the second. For an initialization like std::pair<T,U>{ {}, U{} }, it is viable because it only needs to deduce a type from the second argument. It is also a better match than (C) for the second argument, and hence a better match overall.

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

The constructors (E) and (X) are a libstdc++ extension. I'm not sure if it's compliant, though.

libc++ on the other hand does not have this additional constructors. For the example std::pair<T,U>{ {}, U{} }, it will copy the second argument.

Live demo with both library implementations

like image 56
dyp Avatar answered Sep 30 '22 10:09

dyp