Stupid Reference Tricks

Today’s interesting C++ question comes from one of my students:

class Widget {
private:
  double m_distance;
  ...
};

Widget w;
w.distance() = 62;

Question 1: How does this work?
Question 2: Is this even a good idea?

Question 1 is the easy one to answer. Widget simply provides a distance() member function that returns a non-const reference to m_distance. Ah-like so:

class Widget {
private:
  double m_distance;

public:
  double & distance() { return m_distance; }
};

Widget w;
w.distance() = 62;

Now, w.distance() returns a reference to the m_distance member, and it can be set directly, since a non-const reference can be assigned to.

But Question 2 is the more important one. Is this even a good idea? Well, of course, if you have to ask the question then the answer is probably “no.” But here are a couple of reasons why.

First, this technique interferes with const-correctness in non-obvious ways. Let’s say you need to work with a const Widget:

  void showWidget(const Widget &w) {
    cout << "Distance to widget is " << w.distance() << endl;
  }

This code won’t compile with the above declaration of Widget, because distance() isn’t const! And you can’t declare it const, because then you can’t use it to set m_distance. So, the solution is to do this:

class Widget {
private:
  double m_distance;

public:
  double & distance() { return m_distance; }
  const double & distance() const { return m_distance; }
};

In other words, you have a non-const version of distance() for getting and setting m_distance, and you have a separate const version of distance() when you just need to get the value, in situations that must preserve constness.

Uggg-leee. Now you have two separate methods that are supposed to do the same thing, but you have to keep them in sync.

This is actually the less annoying problem with this kind of approach. The most annoying is that this approach completely destroys your ability to check incoming values for correctness. The technique merges both accessor and mutator into a single function, and unfortunately that leaves you with less control than you would have had if they were two separate member functions.

What about this:

Widget w;
w.distance() = -14.3;

Hmm. Distance probably shouldn’t be negative. But we don’t have any chance to check, because distance() doesn’t get to verify the new value. Now we have a Widget in an invalid state.

Instead, we should do this:

class Widget {
private:
  double m_distance;

public:
  double get_distance() const {
    return m_distance;
  }

  void set_distance(double val) {
    assert(val >= 0);
    m_distance = val;
  }
};

Now everything is as it should be. We can retrieve the value of m_distance, and we can check new values to ensure they are correct.

Leave a Reply