16

Sometimes classes are referencing other classes. Implementing std::swap() for such classes cannot be straightforward, because it would lead to swapping of original instances instead of references. The code below illustrates this behavior:

#include <iostream>

class A
{
   int& r_;
public:
   A(int& v) : r_(v) {}
   void swap(A& a)
   {
      std::swap(r_, a.r_);
   }
};

void test()
{
   int x = 10;
   int y = 20;

   A a(x), b(y);
   a.swap(b);

   std::cout << "x=" << x << "\n"
             << "y=" << y << "\n";
}

int main()
{
    test();
    return 0;
}

A simple workaround with a union:

class A
{
   union
   {
      int& r_;
      size_t t_;
   };
public:
   A(int& v) : r_(v) {}
   void swap(A& a)
   {
      std::swap(t_, a.t_);
   }
};

This is effective, but not handsome. Is there a nicer way to swap two references in C++? Also how does C++ standard explain mixing references and values in one union, considering that in Stroustrup's "The C++ Programming Language" book a 'reference' is defined as an 'alternative name of an object, an alias' (p.189).

timrau
  • 22,578
  • 4
  • 51
  • 64
bkxp
  • 1,115
  • 1
  • 12
  • 20
  • 8
    In short, don't use reference members for objects that should be fully swappable. References aren't pointers. Stop wishing they were. – WhozCraig Sep 12 '14 at 09:25
  • 1
    +1 the question is well written and there is a clear, small and working example. However, do not use it in production code. Follow the advice of Angew – Alessandro Teruzzi Sep 12 '14 at 09:40
  • 2
    What would it mean to "swap two references"? References are aliases for objects, so anything other than swapping the objects would be weird. Thinking of references as pointers only adds confusion. – juanchopanza Sep 12 '14 at 09:43
  • 2
    @juanchopanza: Not exactly. In the same Stroustrup book, properties of references are discussed, and the idea of swapping two references does not contradict any of those properties. It's not like you assign references to each other. The number of objects and the fact that references are initialized remain invariant. – bkxp Sep 12 '14 at 09:48
  • 1
    @bkxp swapping references cannot mean anything other than swapping the objects they refer to. Maybe you can elaborate on how this can have any other meaning. – juanchopanza Sep 12 '14 at 09:52
  • 1
    "This is effective" - no it isn't. A union can't have a reference member. Perhaps your compiler supports it as a (rather dubious) extension, but it's not portable. – Mike Seymour Sep 12 '14 at 10:45
  • Maybe if `swap` become new reserved keyword – Sergei Krivonos May 24 '22 at 15:54

4 Answers4

23

The union trick you're using is about as non-portable as code gets. The standard places no requirements whatsoever on how compilers implement references. They can (and most probably do) use pointers under the hood, but this is never guaranteed. Not to mention the fact that sizeof(size_t) and sizeof(T*) aren't required to be equal anyway.

The best answer to your problem is: don't use reference members if you need an assignable/swappable class. Just use a pointer member instead. After all, references are non-reseatable by definition, yet by wanting the class swappable, you want something reseatable. And that's a pointer.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • 1
    Initially, I expected the compiler to not allow mixing references and non-references in one union. That's where the last part of my question came from. – bkxp Sep 12 '14 at 09:45
  • 3
    @bkxp: The answer to that is that it's not valid, and the compiler shouldn't allow it. Mine doesn't: http://ideone.com/68kzZq – Mike Seymour Sep 12 '14 at 10:42
12

You may use std::reference_wrapper instead of direct reference (which you cannot swap) or pointer (which may be nullptr). Something like:

class A
{
    std::reference_wrapper<int> r;
public:
   A(int& v) : r(v) {}
   void swap(A& rhs)
   {
      std::swap(r, rhs.r);
   }

   int& get() const { return r; }
};

Live example

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • 1
    That's a nice example, thanks. It appears that the standards community have realized the need to swap references and added a workaround to C++11 contrary to the opinion expressed in some other answers here that swapping references is 'bad tone' and should be avoided. It would be interesting to look at how this was implemented. Probably it's a compiler intrinsic, since dot access operator cannot be overloaded and without dot access reference wrappers would have little usability. – bkxp Sep 12 '14 at 14:46
  • 1
    @bkxp: No need of intrinsic, a simple pointer member does the job. – Jarod42 Sep 12 '14 at 14:50
  • 1
    @bkxp This was not added for swapping references; it was added for transporting references into functions through binders in contexts like `std::bind()` and `std::thread`. – Angew is no longer proud of SO Sep 12 '14 at 15:04
  • 1
    @bkxp And note that indeed, you cannot access the members of an object referenced by a reference wrapper by using `.` on the reference wrapper. – Angew is no longer proud of SO Sep 12 '14 at 15:06
  • You might be able to improve this by adding `operator int`. Of course `r` is then read only outside the class. – ThomasMcLeod Sep 17 '14 at 01:45
  • @ThomasMcLeod: Unless required or really useful, I prefer to be explicit with getter (And I assume that `A` may be different that a simple wrapper around `std::reference_wrapper`). But it is true that the *toy* structure may be adapted to needs. – Jarod42 Sep 17 '14 at 07:19
5

Your program using union is not legal, as you assign the reference but swap the integer than intend to use the reference again. You can read about why, here: Accessing inactive union member and undefined behavior?

An easy solution here is to use pointers instead of references. Then everything will work smoothly, with no special code. If you really don't like this, perhaps you can use boost::optional<int&> instead of plain references, but I would struggle to see how that'd be better.

Community
  • 1
  • 1
John Zwinck
  • 239,568
  • 38
  • 324
  • 436
1

Essentially what you would like to do, if I understand your example, is to rebind references. This you cannot do in C++. You can either swap the values in the referred to objects, or you can use pointers.

ThomasMcLeod
  • 7,603
  • 4
  • 42
  • 80