44

I tried to make std::pair with this style:

#include <iostream>

struct A {
    A() noexcept {
        std::cout << "Created\n";
    }
    A(const A&) noexcept {
        std::cout << "Copy\n";
    }
    A(A&&) noexcept {
        std::cout << "Move\n";
    }
};

int main() {
    std::pair<A, A> a{ {},{} };
    return 0;
}

and got such output:

Created
Created
Copy
Copy

instead of

Created 
Created
Move
Move

But if I define my anonymous object type (e.g. std::pair<A, A> a{A{}, A{}}) or use std::make_pair<A, A>({}, {}) I get right result.

std::pair constructor must use std::forward<U1> and std::forward<U2> to initialize objects, thus I think that my pair uses wrong constructor. Why?

cigien
  • 57,834
  • 11
  • 73
  • 112

1 Answers1

55

There's a problem with how std::pair is defined. I'd even say it's a minor defect in the standard.

It has two constructors that could be used here:

  1. pair(const T1 &x, const T2 &y);, where T1, T2 are template parameters of the pair.

  2. template <class U1, class U2> pair(U1 &&x, U2 &&y);

If you do std::pair<A, A> a{A{}, A{}});, then the second constructor is selected and all is well.

But if you do std::pair<A, A> a{{}, {}};, the compiler can't use the second constructor because it can't deduce U1, U2, because {} by itself has no type. So the first constructor is used and you get a copy.

For it to work properly, std::pair should have an extra non-template constructor pair(T1 &&, T2 &&), and for a good measure two extra constructors: pair(const T1 &, T2 &&) and pair(T1 &&, const T2 &).

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
  • 3
    Reminds me of Herb Sutter's Twitter question for the CppCon talk: https://twitter.com/herbsutter/status/1305292734966558721 ‍♂️ – Paul Oct 26 '20 at 16:23
  • 1
    I would rather it have a different one instead of those two it has: `template pair(U1&& x, U2&& y)`. That change might necessitate a new deduction guide though. – Deduplicator Oct 27 '20 at 10:21