Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Tuple question

39 views
Skip to first unread message

David Brown

unread,
Sep 21, 2017, 9:05:00 AM9/21/17
to
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

unread,
Sep 21, 2017, 9:31:30 AM9/21/17
to
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

unread,
Sep 21, 2017, 10:12:48 AM9/21/17
to
On Thursday, September 21, 2017 at 3:05:00 PM UTC+2, David Brown wrote:
>
> 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";

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

unread,
Sep 21, 2017, 10:15:42 AM9/21/17
to
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

unread,
Sep 21, 2017, 10:28:03 AM9/21/17
to
On 21/09/17 16:12, SG wrote:
> On Thursday, September 21, 2017 at 3:05:00 PM UTC+2, David Brown wrote:
>>
>> 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";
>
> 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);

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.

>
> 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.
>

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.)

> Cheers!
> SG
>

Chris Vine

unread,
Sep 21, 2017, 10:57:58 AM9/21/17
to
On Thu, 21 Sep 2017 16:15:19 +0200
David Brown <david...@hesbynett.no> wrote:
[snip]
> 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);
> }

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

unread,
Sep 21, 2017, 11:14:44 AM9/21/17
to
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

unread,
Sep 21, 2017, 1:13:55 PM9/21/17
to
On 21/09/17 16:57, Chris Vine wrote:
> On Thu, 21 Sep 2017 16:15:19 +0200
> David Brown <david...@hesbynett.no> wrote:
> [snip]
>> 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);
>> }
>
> 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.

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.

>
> 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.
>

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.

> Chris
>

0 new messages