- Tuple question - 8 Updates
David Brown <david.brown@hesbynett.no>: Sep 21 03:04PM +0200 What am I missing here? I've been testing with gcc, looking at the generated code. I expected the same code for each of these, but I am not getting it. #include <tuple> int x, y; void f1(void) { std::swap(x, y); } // f1 works as expected void f2(void) { std::tuple<int, int> p = std::tie(x, y); std::tie(y, x) = p; } // f2 works as expected - same code as f1 void f3(void) { auto p = std::tie(x, y); std::tie(y, x) = p; } // Same code as just "x = y"; void f4(void) { std::tie(x, y) = std::tie(y, x); } // Same code as f3, i.e., just "x = y"; |
Chris Vine <chris@cvine--nospam--.freeserve.co.uk>: Sep 21 02:31PM +0100 On Thu, 21 Sep 2017 15:04:39 +0200 > std::tie(x, y) = std::tie(y, x); > } > // Same code as f3, i.e., just "x = y"; The return type of the calls to std::tie above is std::tuple<int&, int&>, which is also the type of 'p' in f3. In f3() and f4() the second call to std::tie in your case assigns the value of x to y, and then the value of y to x, so both hold the original value of x. It could do it the other way round, in which case both would hold the original value of y. The correct approach is to hold the intermediate values in a value type, as in f1() and f2(). Chris |
SG <s.gesemann@gmail.com>: Sep 21 07:12AM -0700 On Thursday, September 21, 2017 at 3:05:00 PM UTC+2, David Brown wrote: > std::tie(x, y) = std::tie(y, x); > } > // Same code as f3, i.e., just "x = y"; As Chris Vine already said, std::tie(x,y) gives you a tuple<int&,int&> instead of a tuple<int,int>. This is necessary for the pattern with tie(...) as the target of an assignment to work as intended. What you could write instead is tie(x,y) = make_tuple(y,x); Here, make_tuple creates a tuple<int,int> with copied values. In C++17 you can also use tuple's constructor like this tie(x,y) = tuple(y,x); because this class template argument deduction gives you a tuple of values, too. Cheers! SG |
David Brown <david.brown@hesbynett.no>: Sep 21 04:15PM +0200 On 21/09/17 15:31, Chris Vine wrote: > the other way round, in which case both would hold the original value > of y. The correct approach is to hold the intermediate values in a > value type, as in f1() and f2(). Thank you. Once you pointed out that std::tie returns a tuple of references (which of course it must for it to make sense as an lvalue), it suddenly made sense. For some reason I was thinking of the std::tie on the right-hand side as a value type - std::tuple<int, int> - rather than a reference type. This works correctly: void f5(void) { std::tie(x, y) = std::make_tuple(y, x); } |
David Brown <david.brown@hesbynett.no>: Sep 21 04:27PM +0200 On 21/09/17 16:12, SG wrote: > tie(...) as the target of an assignment to work as intended. > What you could write instead is > tie(x,y) = make_tuple(y,x); Yes, I realised that after reading Chris's post, and shortly before you made your reply. I had just been playing around with tuples and ties, and got myself confused. > tie(x,y) = tuple(y,x); > because this class template argument deduction gives you a tuple of > values, too. C++17 also allows: auto [a, b] = std::tuple(y, x); std::tie(x, y) = std::tuple(a, b); (And one of these "std::tuple" can be "std::tie", but not both. It all makes sense now.) |
Chris Vine <chris@cvine--nospam--.freeserve.co.uk>: Sep 21 03:57PM +0100 On Thu, 21 Sep 2017 16:15:19 +0200 David Brown <david.brown@hesbynett.no> wrote: [snip] > void f5(void) { > std::tie(x, y) = std::make_tuple(y, x); > } That will work fine but I would still adopt the f1() approach (std::swap) which will only carry out two moves and one copy (or three copies for built-in types with no move constructor) whereas the above in theory will do two copies and two moves. With built-in types such as ints there will be no difference because both will be optimized into the same object code, but it might make a difference with class types with non-trivial constructors. Having said that, I can't keep up with the extent to which optimizing out non-trivial constructor calls is allowed these days except in RVO cases: possibly C++17 gives more leeway, I'm not sure. You would like to think so. Chris |
Chris Vine <chris@cvine--nospam--.freeserve.co.uk>: Sep 21 04:14PM +0100 On Thu, 21 Sep 2017 15:57:42 +0100 > out non-trivial constructor calls is allowed these days except in RVO > cases: possibly C++17 gives more leeway, I'm not sure. You would like > to think so. On thinking about it I guess std::swap could do three moves and no copy: x to temp, y to x, and temp to x. This would only be exception safe if the move constructors/move assignment operators are noexcept; but swapping isn't exception safe anyway without help from the swapped types. Chris |
David Brown <david.brown@hesbynett.no>: Sep 21 07:13PM +0200 On 21/09/17 16:57, Chris Vine wrote: > ints there will be no difference because both will be optimized into > the same object code, but it might make a difference with class types > with non-trivial constructors. Oh, absolutely. When swapping, std::swap is the first choice. I was merely playing around to see how things were working with std::tie and related things, and hit a mental block. > out non-trivial constructor calls is allowed these days except in RVO > cases: possibly C++17 gives more leeway, I'm not sure. You would like > to think so. There is always the "as if" rule - if the compiler can see there is no visible difference to the program if constructor calls are "optimised out", then it is free to do so. |
You received this digest because you're subscribed to updates for this group. You can change your settings on the group membership page. To unsubscribe from this group and stop receiving emails from it send an email to comp.lang.c+++unsubscribe@googlegroups.com. |
No comments:
Post a Comment