Thursday, September 21, 2017

Digest for comp.lang.c++@googlegroups.com - 8 updates in 1 topic

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: