3

I'm just reading a bit about tuples.

Now the following syntax is not clear to me:

std::tie (myint, std::ignore, mychar) = mytuple;

It's not difficult to grasp what it does, BUT what happens from a language point of view? We are somehow assigning to the return value of a function?

ildjarn
  • 62,044
  • 9
  • 127
  • 211
Michael
  • 7,407
  • 8
  • 41
  • 84

4 Answers4

6

BUT what happens from a language point of view? We are somehow assigning to the return value of a function?

Yes, that can be valid, depending on the function's return type. There are mainly two ways in which it can be valid: firstly, the function may return an lvalue reference to an object.

int i;
int &f() { return i; }
int main() { f() = 1; } // okay, assigns to i

Secondly, functions may return user-defined types with an = operator implementation that can be called on rvalues:

struct S { void operator=(int) { } };
S f() { return {}; }
int main() { f() = 1; } // okay, calls S::operator=

The latter is what's happening with std::tie.

  • Note that for C++ `struct`s and `class`es, until rvalue references to `*this` showed up in C++11, could always be assigned to even if a temporary! `operator=` would work, even if the result was discarded. The interesting part here is that assigning to a `tie` does something *useful*. – Yakk - Adam Nevraumont Nov 21 '16 at 00:15
5

The return type of std::tie(myint, std::ignore, mychar) is
std::tuple<int&, decltype((std::ignore)), char&>, wherein the int& is a reference to myint and the char& is a reference to mychar.

When mytuple is assigned to this returned tuple-of-references, each value in mytuple is assigned to the respective reference stored in the returned tuple. This has the effect of updating myint and mychar in place.

std::tie(myint, std::ignore, mychar)             // <-- expression
std::tuple<int&, decltype((std::ignore)), char&> // <-- type

std::tie(myint, std::ignore, mychar)             = mytuple;
std::tuple<int&, decltype((std::ignore)), char&> = std::tuple<int, T, char>&;
// functions as
            std::tuple<int , T                      , char >&
//          ↓↓         =     =                        =    ↓↓
            std::tuple<int&, decltype((std::ignore)), char&>

// end result:
myint  = std::get<0>(mytuple);
mychar = std::get<2>(mytuple);
int&   = int&;
char&  = char&;
ildjarn
  • 62,044
  • 9
  • 127
  • 211
3

tie returns a tuple of references. You're assigning to that tuple, which means tuple-memberwise assignment (with the exception with std::ignored fields). Because that tuple's elements are actually references, you're assigning to the tied elements instead.

krzaq
  • 16,240
  • 4
  • 46
  • 61
2

From cpp reference "Creates a tuple of lvalue references to its arguments or instances of std::ignore. "

In that sense, it's not that different from when you assign to the return value of operator [] as in

vec[3]=5;

We just have to mention that C++17 has structured bindings auto [a,b,c] =, and std::ignore with structured bindings?

Community
  • 1
  • 1
Johan Lundberg
  • 26,184
  • 12
  • 71
  • 97