1

I've found this paper implementing a C++ proxy wrapper class, which allows Code execution before and after each method call by overloading operator->.

In Section 11, Techniques it is mentioned that it is possible to chain wrapper classes into each other, like

#include <iostream>
using namespace std;

template<class T, class Pref, class Suf> class Wrap;
template<class T, class Suf>
class Call_proxy {
  T* p;
  mutable bool own;
  Suf suffix;
  Call_proxy(T* pp, Suf su) :p(pp) , own(true) , suffix(su) { } // restrict creation
  Call_proxy& operator=(const Call_proxy&) ; // prevent assignment
  public:
  template<class U, class P, class S> friend class Wrap;
  Call_proxy(const Call_proxy& a) : p(a.p) , own(true) , suffix(a.suffix) { a.own=false; }
  ~Call_proxy() { if (own) suffix() ; }

  T* operator->() const{ return p;} // error: ‘struct Shared<X>’ has no member named ‘g’
  //T& operator->() const{ return *p;} // error: result of ‘operator->()’ yields non-pointer result
};


template<class T, class  Pref, class Suf>
class  Wrap {
  T& p;
  Pref prefix;
  Suf suffix;
public:
  Wrap(T& x,  Pref pr, Suf su) :p(x) , prefix(pr) , suffix(su) { }
  Call_proxy<T,Suf> operator->() const{ 
      prefix() ; 
      return  Call_proxy<T,Suf>(&p,suffix); 
  }
};

void prefix() { cout << "prefix "; }
void suffix() { cout << " suffix\n"; }
struct Pref { void operator()() const{ cout<< " Pref "; } };
struct Suf { void operator()() const{ cout<< " Suf "; } };

template<class T> struct Shared : public Wrap<T,Pref, Suf> {
   Shared(T& obj) : Wrap<T,Pref, Suf>(obj,Pref() , Suf()) { }
};
template<class T> struct Tracer : public Wrap<T,void(*)() ,void(*)()>  { 
    Tracer(T& x) : Wrap<T,void(*)() ,void(*)()>(x,::prefix,::suffix) { } 
};

class X {
public:
    void g() const{ cout << "g()"; }
};

int main() {// test program
  X x;
  Shared<X> xx(x) ;
  Tracer<Shared<X>> xxx(xx);

  xx->g();
  xxx->g();
  return 0;
}

But this fails with the error error: ‘struct Shared<X>’ has no member named ‘g’. I read about operator-> overloading and understand the problem (Call_proxy returns a pointer, which does not propagate the overloading, as mentioned here).

I tried some alternatives by returning reference instead of pointer, but in the end I have either the problem mentioned above, or the problem that operator-> is called on a reference type (error: result of ‘operator->()’ yields non-pointer result).

Is there a way to achieve this? As it was mentioned in the paper, I thought it should be possible.


Edit: Code versions for both errors.

Both versions are the same, with line 17 or line 18 commented out.

Community
  • 1
  • 1
MatthiasB
  • 1,759
  • 8
  • 18

3 Answers3

1

operator-> must return either a pointer type or a class type with overloaded operator->. When it returns a class type with overloaded operator->, chaining occurs. When it returns a pointer, chaining terminates.

If Call_proxy::operator-> returns T* then these chains are evaluated as follows:

   Shared<X> xx(x) ;
// xx->g();
   xx.Wrap<X,Pref,Suf>::operator->()   // Returns Call_proxy<X,Suf>
     .Call_proxy<X,Suf>::operator->()  // Returns X*
     ->X::g();

   Tracer<Shared<X>> xxx(xx);
// xxx->g();
   xxx.Wrap<X,void(*)(),void(*)()>::operator->()  // Returns Call_proxy<X,void(*)(),void(*)()>
      .Call_proxy<X,void(*)()>::operator->()      // Returns Shared<X>*
      ->Shared<X>::g();

The first call is fine, the second call fails because chaining terminates with Shared<X>* and Shared<X> doesn't have a g().

If Call_proxy::operator-> returns T& then the chains are evaluated as follows:

   Shared<X> xx(x) ;
// xx->g();
   xx.Wrap<X,Pref,Suf>::operator->()   // Returns Call_proxy<X,Suf>
     .Call_proxy<X,Suf>::operator->()  // Returns X&
     .X::operator->(); // ???

   Tracer<Shared<X>> xxx(xx);
// xxx->g();
   xxx.Wrap<X,void(*)(),void(*)()>::operator->()  // Returns Call_proxy<X,void(*)(),void(*)()>
      .Call_proxy<X,void(*)()>::operator->()      // Returns Shared<X>&
      .Shared<X>::operator->()                    // Call_proxy<X,Suf>
      .Call_proxy<X,Suf>::operator->()            // Returns X&
      .X::operator->(); // ???

Both calls fail becuase X is neither a pointer type nor a class type with overloaded operator->.

For this design to work, Call_proxy::operator-> needs to somehow distinguish between whether T is a proxy class or not, and return T& if it is and T* if it is not.

Oktalist
  • 14,336
  • 3
  • 43
  • 63
0

As stated in my question, and elaborated by @Oktalist, the problem is to distinguish between classes having an operator->, and classes which don't. There are 2 approaches to this.

The first, and easiest solution is to have a controlled hierarchy, where the innermost class always returns a pointer type. An implementation would look like this:

template<class T> struct Sealed {
  T&p;
  Sealed(T&x ) : p(x){}
  T* operator->() {return &p;}
};

If T is owned, a std::shared_ptr<T> can be used, which has the same effect.

int main() {
  X x;
  Sealed<X> xx(x);
  Shared<Sealed<X>> xxx(xx) ;
  Tracer<Shared<Sealed<X>>> xxxx(xxx);

  auto y = std::make_shared<X>();
  Shared<decltype(y)> yy(y);
  Tracer<decltype(yy)>yyy(yy);
}

The second option is to let Call_proxy have different implementations depending on wether or not a class T has an implementation for operator->. In my opinion this is more elegant and less error-prone for a user, but more complicated to implement. Also, I didn't find a way to implement this without C++11 features, which i cannot use unfortunately.

The implementation uses a SFINAE checker to check if operator-> is present for a class:

template <typename T>
class has_operator
{
    typedef char yes;
    typedef char no[2];
  template <typename I> static I identity(I);

  template<typename U,U> struct Check;
  template <typename C> static yes& test(Check<decltype(identity(&C::operator->)),&C::operator-> >*);
    template <typename C> static no& test(...);

public:
    enum { value = sizeof(test<T>(0)) == sizeof(yes) };
};

depending on the result, Call_proxy can be template-specialised; this can be moved to a top class.

template<class T, bool hasOperator = true/*has_operator<T>::value*/>
struct PtrOperator {
  T* p;
  PtrOperator(T*pp) : p(pp) {}
  T* operator->() const { return p;}
};

template<class T> 
struct PtrOperator<T,true>{
  T* p;
  PtrOperator<T,true>(T*pp) : p(pp) {}
  T& operator->() const { return *p;}
};

template<class T, class  Pref, class Suf> class  Wrap;
template<class T, class Suf>
class  Call_proxy : public PtrOperator<T>{
  mutable bool own;
  Suf suffix;
  Call_proxy(T* pp, Suf su) : PtrOperator<T>(pp) , own(true) , suffix(su) { }  // restrict creation
  Call_proxy& operator=(const  Call_proxy&) ;  // prevent assignment
public:
  template<class  U, class  P, class S> friend class  Wrap;
  Call_proxy(const  Call_proxy& a) : PtrOperator<T>(a.p) , own(true) , suffix(a.suffix) { a.own=false; }
  ~Call_proxy() { if (own) suffix() ; }
};

Using this, there is no need for a special class in the end. Any object can be wrapped, and the chaining of operator-> works in all cases.

int main() {
  Shared<X> z(x) ;
  Tracer<Shared<X>> zz(z);

  x.g();
  z->g();
  zz->g();
}

I've uploaded the full example to http://ideone.com/byQYR1

MatthiasB
  • 1,759
  • 8
  • 18
-1

Your Wrapper operator-> is hidden by your Tracer class by the default functions.

You can solve this issue as described here: Why does an overloaded assignment operator not get inherited?

(You also could try to set the operator-> function to virtual inside of your Wrap class, but i'm not sure if that will work.)

Community
  • 1
  • 1
pcaaron
  • 37
  • 4
  • I think there is no default `operator->` which could hide the implementation of the base class. I've tested it [here](http://ideone.com/AOeiRk), but it does not make any difference. The problem is that `operator->` returns a pointer type, which stops propagation – MatthiasB Sep 22 '14 at 13:59