Search code examples
c++templatesinitializationmove-semanticsstd-pair

Copy vs move in std::pair braced initialization


I have the following code and its output printed below. I can't seem to understand why one set of braced initialization results in the move constructor being called, while the other results in the copy constructor. I have somewhat narrowed it down to direct-list-initialization vs copy-list-initialization per https://en.cppreference.com/w/cpp/language/list_initialization I just can't quite figure out which case my code belongs to. Thanks in advance.

#include <cstdint>
#include <iostream>
using namespace std;

struct Foo {
  Foo() {
    cout << "create foo\n";
  }

  ~Foo() {
    cout << "delete foo\n";
  }

  Foo(const Foo& f) {
    cout << "copy foo\n";
  }

  Foo(Foo&& f) noexcept {
    cout << "move foo\n";
  }

  Foo& operator=(const Foo& f) = delete;

  Foo& operator=(Foo&& f) = delete;
};

int32_t main() {
  pair<uint32_t, Foo> f1{0, Foo{}};  // Calls move ctor
  cout << "------------------------\n";

  pair<uint32_t, Foo> f2{0, {}};     // Calls copy ctor
  cout << "------------------------\n";

  return 0;
}

This results in

create foo

move foo

delete foo

------------------------

create foo

copy foo

delete foo

------------------------

delete foo

delete foo

Solution

  • Let's take a look at two of the two-argument constructors of pair: [pairs.pair]

    EXPLICIT constexpr pair(const T1& x, const T2& y);
    template<class U1, class U2> EXPLICIT constexpr pair(U1&& x, U2&& y);
    

    The second constructor uses perfect forwarding, and U2 cannot be deduced from {}. Therefore, the first version is selected when {} is used. When Foo{} is used instead, the argument has type Foo, so U2 is deduced to be Foo, causing the forwarding version to be selected.