Read-only proxy class for primitives in C++

Alec Jacobson

October 14, 2014

weblog/

One of my least favorite experiences coding in C++ is writing boilerplate setters and getters. Usually I won't write setters unless I need side effects. For that, read-and-write case I'll just use public member functions. The most common case I run into is when I have members I'd like to expose as read-only. That is, I want to be able to get the value of a private or protected member. So I'll end up with something like this:

class MyFancyClass
{
private:
  int m_var1;
  double m_var2;
  ...
  double m_varn;
public:
  int get_var1(){return m_var1}
  double get_var2(){return m_var2}
  ...
  double get_varn(){return m_varn}
} a;

This is annoying. Anytime I add a new member m_varnplus1 I'll need to add a corresponding MyFancyClass::get_varnplus1(). It's also clunky from the caller's point of view. I can't just use something like x = a.m_var1, instead I need to use the caller x = a.get_var1(). Often I find myself getting to lazy to do all this so I just sloppily use:

class MyFancyClass
{
public:
  int m_var1;
  double m_var2;
  ...
  double m_varn;
} a;

Boom. Fuck it. Everything's gonna be public.

I'm toying with the idea of widely employing this proxy class for primitives. I'd save the following in RO.h:

#ifndef RO_H
#define RO_H
// A proxy class for creating "read only" members: member 
// 
// adapted from http://stackoverflow.com/a/22775057/148668
template<typename Container, typename Primitive>                                       
class RO
{
  friend Container;
public:
                           inline operator Primitive() const             { return x; }
  template<typename Other> inline bool  operator==(const Other& y) const { return x == y; } 
  template<typename Other> inline bool  operator!=(const Other& y) const { return x != y; } 
  template<typename Other> inline bool  operator< (const Other& y) const { return x < y; } 
  template<typename Other> inline bool  operator> (const Other& y) const { return x > y; } 
  template<typename Other> inline bool  operator<=(const Other& y) const { return x <= y; } 
  template<typename Other> inline bool  operator>=(const Other& y) const { return x >= y; } 
  template<typename Other> inline Other operator+ (const Other& y) const { return x + y; } 
  template<typename Other> inline Other operator- (const Other& y) const { return x - y; } 
  template<typename Other> inline Other operator* (const Other& y) const { return x * y; }  
  template<typename Other> inline Other operator/ (const Other& y) const { return x / y; } 
  template<typename Other> inline Other operator<<(const Other& y) const { return x <<y; }
  template<typename Other> inline Other operator>>(const Other& y) const { return x >> y; }
  template<typename Other> inline Other operator^ (const Other& y) const { return x ^ y; }
  template<typename Other> inline Other operator| (const Other& y) const { return x | y; }
  template<typename Other> inline Other operator& (const Other& y) const { return x & y; }
  template<typename Other> inline Other operator% (const Other& y) const { return x % y; }
  template<typename Other> inline Other operator&&(const Other& y) const { return x && y; }
  template<typename Other> inline Other operator||(const Other& y) const { return x || y; }
  template<typename Other> inline Other operator~() const                { return ~x; }
  template<typename Other> inline Other operator!() const                { return !x; }
protected:
                           inline RO()                                  :x(){ }
  template<typename Other> inline RO(const Other& y)                    :x(y) { }
  template<typename Other> inline Primitive& operator= (const Other& y) { return x = y; }       
  template<typename Other> inline Primitive& operator+=(const Other& y) { return x += y; }      
  template<typename Other> inline Primitive& operator-=(const Other& y) { return x -= y; }      
  template<typename Other> inline Primitive& operator*=(const Other& y) { return x *= y; }      
  template<typename Other> inline Primitive& operator/=(const Other& y) { return x /= y; }      
  template<typename Other> inline Primitive& operator&=(const Other& y) { return x &= y; }
  template<typename Other> inline Primitive& operator|=(const Other& y) { return x |= y; }
  template<typename Other> inline Primitive& operator%=(const Other& y) { return x %= y; }
  template<typename Other> inline Primitive& operator++()               { return ++x; }      
                           inline Primitive  operator++(int)            { return x++; }      
  template<typename Other> inline Primitive& operator--()               { return --x; }      
                           inline Primitive  operator--(int)            { return x--; }      
                           inline Primitive* ptr()                      { return &x; }      
                           inline Primitive& ref()                      { return x; }      
                           inline const Primitive* const_ptr() const    { return &x; }      
                           inline const Primitive& const_ref() const    { return x; }      
  Primitive x;                                                                                
}; 

#endif

Then I could use it with something like:

class MyFancyClass
{
  typedef MyFancyClass Self;
public:
  RO<Self,int> m_var1;
  RO<Self,double> m_var2;
  ...
  RO<Self,double> m_varn;
  void example_of_write_access()
  {
    m_var1 = 10;
    m_var2 = 100;
    m_varn = m_var2++;
  }
} a;

Outside this class I'm only allowed read operations int x = a.m_var1 or if(a.m_var1) etc. But I can't write to these members: can't write a.m_var2 = 3 or a.m_varn += 7.

I can use my members as usual inside my class with full read and write access: e.g. m_var1 = 10 or m_var2++.

These read only members must be declared public to be readable outside the container class. Declaring them private would be pointless. However, declaring them protected will make them read-only to friends of the container. Thus the public/protected/private class access modifiers effectively determine the read-access, and write-access is always private.

Lingering issue: I'm not sure how you'd create a publicly read-only member who's write-access was protected (shared with friends and derived classes). Somehow I'd have to convince RO to be friends also with the inherited class, which isn't known during the definition of the base container class. Seems since friendship is neither inherited nor transitive, this is not going to be easy.