Can one tuple element refer to another?

Update: See full answer below. The short answer is no, not directly. You can create an indirect link using std::reference_wrapper or perform a more general effect with pointers (but without syntactic sugar and additional link security).

I ask because tuples make convenient variational memory in C ++ 11. It is theoretically reasonable for one element of a tuple to contain a link to another element in one tuple. (Replace the "link" with "pointer", and it works in practice.) The devil is the details of constructing such a tuple. Consider the following example:

 #include <tuple> #include <iostream> class A { public: A() : val(42) { } int val; }; class B { public: B(A &a) : _a(a) { } int val() { return _a.val; } private: A &_a; }; int main() { A a; B b(a); std::tuple<A, B> t1(a, b); a.val = 24; std::cout << std::get<0>(t1).val << "\n"; // 42 std::cout << std::get<1>(t1).val() << "\n"; // 24 return 0; } 

The second element in t1 tuple refers to the automatic variable a instead of the first element in t1 . Is there a way to build a tuple so that one element of the tuple can contain a link to another element in the same tuple? I know that you could achieve this result by creating a tuple of links, for example:

 int main() { A a; B b(a); std::tuple<A &, B &> t2 = std::tie(a, b); a.val = 24; std::cout << std::get<0>(t2).val << "\n"; // 24 std::cout << std::get<1>(t2).val() << "\n"; // 24 return 0; } 

But for my purposes, this is a hoax, as the second element in t2 still refers to an object that lives outside the tuple. The only way I can think that it compiles, but may contain undefined behavior [Edited to display a more concise example provided by Howard Hinnant]:

 int main() { std::tuple<A, B> t3( A(), B(std::get<0>(t3)) ); // undefined behavior? std::get<0>(t3).val = 24; std::cout << std::get<0>(t3).val << "\n"; std::cout << std::get<1>(t3).val() << "\n"; // nasal demons? } 

Edit: here is a minimal test program that returns with non-zero exit status when compiling using g ++ 4.7 with -O2 or higher. This assumes either undefined behavior or an error in gcc.

 #include <tuple> class Level1 { public: Level1() : _touched(false), _val(0) { } void touch() { _touched = true; } double feel() { if ( _touched ) { _touched = false; _val = 42; } return _val; } private: bool _touched; double _val; }; class Level2 { public: Level2(Level1 &level1) : _level1(level1) { } double feel() { return _level1.feel(); } private: int _spaceholder1; double _spaceholder2; Level1 &_level1; }; class Level3 { public: Level3(Level2 &level2) : _level2(level2) { } double feel() { return _level2.feel(); } private: Level2 &_level2; }; int main() { std::tuple<Level3, Level2, Level1> levels( Level3(std::get<1>(levels)), Level2(std::get<2>(levels)), Level1() ); std::get<2>(levels).touch(); return ! ( std::get<0>(levels).feel() > 0 ); } 
+8
c ++ undefined-behavior c ++ 11 tuples
source share
2 answers

This works for me:

 #include <tuple> #include <iostream> int main() { std::tuple<int&, int> t(std::get<1>(t), 2); std::cout << std::get<0>(t) << '\n'; std::get<1>(t) = 3; std::cout << std::get<0>(t) << '\n'; } 

Update

I just asked about this case on the CWG mailing list. Mike Miller assures me that this behavior undefined on 3.8p6 bullet 2:

... A program has undefined behavior if:

...

  • glvalue is used to access a non-static data element or to call a non-static member function of an object, or

...

The behavior would be correctly defined if tuple was an aggregate, but since tuple has a constructor declared by the user, 3.8p6b2 is applied.

However, this works and avoids UB:

 #include <tuple> #include <functional> #include <cassert> int main() { int dummy; std::tuple<std::reference_wrapper<int>, int> t(dummy, 2); std::get<0>(t) = std::get<1>(t); assert(std::get<0>(t) == 2); std::get<1>(t) = 3; assert(std::get<0>(t) == 3); } 
+5
source share

To my knowledge, this is a well-defined behavior for the alias of an object that does not yet exist. This is the same situation that you can find with this in the constructor initialization list, which is unsuccessful, but general and well-defined.

0
source share

All Articles