C++11 use-case for piecewise_construct of pair and tuple?
Asked Answered
A

2

52

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?
Autumn answered 28/5, 2011 at 14:23 Comment(1)
Try also disabling the move constructor.Mariejeanne
B
57

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});
Bellarmine answered 28/5, 2011 at 17:40 Comment(7)
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: <br> template<class... _Args1, class... _Args2> pair(piecewise_construct_t, tuple<_Args1...> __first, tuple<_Args2...> __second) : first(__cons<first_type>(std::move(__first))), second(__cons<second_type>(std::move(__second))) { } As you can see, a move is required. emplace does not seem to be used.Autumn
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?).Autumn
@Autumn : 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. @Bellarmine : 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)Statecraft
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.Hahn
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<Args1...> first_args, std::tuple<Args2...> second_args );Simons
std::pair<big, big> pair(piecewise_construct, {1,2}, {3,4}); does not compiles tooTalmudist
@ustcyue: Copy elision is mandatory since C++17 for functions that are returning values. As this subject is about constructors and those not being functions, copy elision is out of the question here.Adp
D
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));
}
Damar answered 12/12, 2021 at 15:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.