Sunday, April 11, 2004

There are two lines of thoughts for pointers vs. references. One says to use pointers when something can be null and references elsewhere. The other says to use pass pointers when something is mutable and pass const references when it's immutable, because if you use non-const reference parameters it's hard to tell at the caller that something might be mutated.

I tend to prefer using references almost all the time. So I was looking for something that could address the mutability issue without my having to switch to pointers. Here's one solution:

  template<typename T>
  struct receivable {
    explicit receivable(T& receiver): receiver_(receiver) {}
    T& get() { return receiver_; }
    operator T& () { return receiver_; }
    T& operator = (const T& v) { receiver_ = v; return receiver_; }
   private:
    T& receiver_;
  };

  template<typename T>
  receivable<T> receive(T& receiver) {
    return receivable<T>(receiver);
  }

It's a small helper class that annotates the argument. Instead of the caller using f(x) for both immutable and mutable parameters, it'll use f(x) for immutable and f(receive(x)) for a mutable parameter. Here's some sample code:

void f(receivable<int> i) {
  i = 5;
}
 
int main() {
  int j;
  f(j);              // this line causes a compile-time error
  f(receive(j));      // this line works
}

Just as auto_ptr is the standard way to use the type system to mark the transfer of ownership, receivable is a way to use the type system to mark that an argument may be mutated. There may be other kinds of annotations that can use the same technique.