51

In N3059 I found the description of piecewise construction of pairs (and tuples) (and it is in the new Standard).

But I can not see when I should use it. I found discussions about emplace and non-copyable entities, but when I tried it out, I could not create a case where I need piecewiese_construct or could see a performance benefit.

Example. I thought I need a class which is non-copyable, but movebale (required for forwarding):

struct NoCopy {
  NoCopy(int, int) {};
  NoCopy(const NoCopy&) = delete; // no copy
  NoCopy& operator=(const NoCopy&) = delete; // no assign
  NoCopy(NoCopy&&) {}; // please move
  NoCopy& operator=(NoCopy&&) {}; // please move-assign
};

I then sort-of expected that standard pair-construction would fail:

pair<NoCopy,NoCopy> x{ NoCopy{1,2}, NoCopy{2,3} }; // fine!

but it did not. Actually, this is what I'd expected anyway, because "moving stuff around" rather then copying it everywhere in the stdlib, is it should be.

Thus, I see no reason why I should have done this, or so:

pair<NoCopy,NoCopy> y(
    piecewise_construct,
    forward_as_tuple(1,2),
    forward_as_tuple(2,3)
); // also fine
  • So, what's a the usecase?
  • How and when do I use piecewise_construct?
chema989
  • 3,962
  • 2
  • 20
  • 33
towi
  • 21,587
  • 28
  • 106
  • 187

2 Answers2

55

Not all types can be moved more efficiently than copied, and for some types it may make sense to even explicitly disable both copying and moving. Consider std::array<int, BIGNUM> as an an example of the former kind of a type.

The point with the emplace functions and piecewise_construct is that such a class can be constructed in place, without needing to create temporary instances to be moved or copied.

struct big {
    int data[100];
    big(int first, int second) : data{first, second} {
        // the rest of the array is presumably filled somehow as well
    }
};

std::pair<big, big> pair(piecewise_construct, {1,2}, {3,4});

Compare the above to pair(big(1,2), big(3,4)) where two temporary big objects would have to be created and then copied - and moving does not help here at all! Similarly:

std::vector<big> vec;
vec.emplace_back(1,2);

The main use case for piecewise constructing a pair is emplacing elements into a map or an unordered_map:

std::map<int, big> map;
map.emplace(std::piecewise_construct, /*key*/1, /*value*/{2,3});
JohannesD
  • 13,802
  • 1
  • 38
  • 30
  • 4
    You described that quite nicely, thanks. And thate quite what I thought, too. But `pair(piecewise_construct,...)` does not work if you **disable moving on the target class** (with `=delete`). I tried it with gcc-4.7.0 and *got a compile error*. reason: implementation of `pair`:
    `template pair(piecewise_construct_t, tuple<_Args1...> __first, tuple<_Args2...> __second) : first(__cons(std::move(__first))), second(__cons(std::move(__second))) { }` As you can see, a **move** is required. **emplace** does not seem to be used.
    – towi May 29 '11 at 10:27
  • Ok, it seems that gcc-4.7.0 does not yet offer `map.emplace()`. But this still leaves my question about the `pair(piecewise_construct, {...},{...})`. Should this work with *non-copyable* **and** *non-movable* objects? From the formulation in the Std 20.3.2.(14) it might seem that way. Although "forwarding" is mehtioned, it seems to apply on the arguments of the constructors and not on the objects itself (where then a move could be needed?). – towi May 29 '11 at 10:46
  • @towi : I think gcc-4.7.0+libstdc++ is non-compliant yet for pair constructor with piecewise_construct. Non-copyable and non-movable object should work and for example clang+libc++ give the right result. @JohannesD : I don't understand why you think map.emplace should work with piecewise_construct ? Every proposal that I'm aware of seem to imply that the usage of emplace should be `map.emplace(Key, ArgForValue...)`, so for example `map.emplace(/*key*/1, /*value*/ 2, 3);` (and that's how clang+libc++ behave) – Arzar May 30 '11 at 00:03
  • about the example of `pair(big(1,2), big(3,4))`, it should be optimized by copy elision right? the constructed object `big(1, 2)` has no other reference name except the pair. – ustcyue May 11 '20 at 22:04
  • 3
    `map.emplace(std::piecewise_construct, /*key*/1, /*value*/{2,3});` does not compile, instead it should be used `map.emplace(std::piecewise_construct, std::forward_as_tuple(1), std::forward_as_tuple(2,3));` as the appropriate constructor of `std::pair` is `template< class... Args1, class... Args2 > pair(std::piecewise_construct_t, std::tuple first_args, std::tuple second_args );` – Miljen Mikic Aug 18 '20 at 10:01
  • 1
    `std::pair pair(piecewise_construct, {1,2}, {3,4});` does not compiles too – 김선달 Dec 17 '20 at 05:49
1

One power piecewise_construct has is to avoid bad conversions when doing overload resolution to construct objects.

Consider a Foo that has a weird set of constructor overloads:

struct Foo {
    Foo(std::tuple<float, float>) { /* ... */ }
    Foo(int, double) { /* ... */ }
};

int main() {
    std::map<std::string, Foo> m1;
    std::pair<int, double> p1{1, 3.14};

    m1.emplace("Will call Foo(std::tuple<float, float>)",
               p1);

    m1.emplace("Will still call Foo(std::tuple<float, float>)",
               std::forward_as_tuple(2, 3.14));

    m1.emplace(std::piecewise_construct,
               std::forward_as_tuple("Will call Foo(int, double)"),
               std::forward_as_tuple(3, 3.14));

    // Some care is required, though...
    m1.emplace(std::piecewise_construct,
               std::forward_as_tuple("Will call Foo(std::tuple<float, float>)!"),
               std::forward_as_tuple(p1));
}
FeRD
  • 1,699
  • 15
  • 24