Search code examples
c++c++11design-patternsdereferenceidioms

Why the `T* operator->()` is applied repeatedly even if written once?


Why the T* operator->() is applied repeatedly even if written once? But another T& operator*() is applied once, and should be written many times.

As known there is Execute-Around Pointer Idiom in C++. More C++ Idioms/Execute-Around Pointer

Provide a smart pointer object that transparently executes actions before and after each function call on an object, given that the actions performed are the same for all functions. And before and after each treatment to member variable of a class. For example we can performs:

  • lock mutex
  • log action
  • visualize changing data

I added some in main() to this example:

#include <iostream>
#include <vector>

class VisualizableVector {
  public:
    class proxy {
      public:
        proxy (std::vector<int> *v) : vect (v) {
            std::cout << "Before size is: " << vect->size() << std::endl;
        }
        std::vector<int> * operator -> () { return vect; }
        std::vector<int> & operator * () { return *vect; }
        ~proxy () { std::cout << "After size is: " << vect->size() << std::endl; }
      private:
        std::vector <int> * vect;
    };        
    VisualizableVector (std::vector<int> *v) : vect(v) {}            
    ~VisualizableVector () { delete vect; }   
    proxy operator -> () { return proxy (vect); }
    proxy operator * () { return proxy (vect); }
  private:
    std::vector <int> * vect;
};

int main()
{
  VisualizableVector vecc (new std::vector<int>);

  vecc->push_back (10);         // 1. Note use of -> operator instead of . operator      
  vecc->push_back (20);         // 2. ok      
  (*vecc)->push_back (30);      // 3. ok      
  // (*vecc).push_back (40);    // 4. error      
  (**vecc).push_back (50);      // 5. ok      
  // vecc->->push_back (60);    // 6. error     
}

Online compiler result: http://ideone.com/cXGdxW

Why do we need to write twice **, but only once -> ?

Its operator return the same thing proxy:

    proxy operator -> () { return proxy (vect); }
    proxy operator * () { return proxy (vect); }

But why do we need to use * again, but we shouldn't use -> again?:

  vecc->push_back (20);     // 2. ok      (vecc->) is proxy
  (**vecc).push_back (50);  // 5. ok      (*vecc) is proxy

Why not vecc->->push_back (20);?

Is there anything about this in the standard C++ (03/11/14)?

UPDATE:

In different cases we should use 1,2 or 3 operator->s : http://ideone.com/89kfYF

#include <iostream>
#include <vector>    
class VisualizableVector {
  public:
    class proxy {
      public:
        proxy (std::vector<int> *v) : vect (v) {
            std::cout << "Before size is: " << vect->size() << std::endl;
        }
        std::vector<int> * operator -> () { return vect; }
        std::vector<int> & operator * () { return *vect; }
        ~proxy () { std::cout << "After size is: " << vect->size() << std::endl; }
      private:
        std::vector <int> * vect;
    };        
    VisualizableVector (std::vector<int> *v) : vect(v) {}            
    ~VisualizableVector () { delete vect; }   
    proxy operator -> () { return proxy (vect); }
    proxy operator * () { return proxy (vect); }
  private:
    std::vector <int> * vect;
};

int main()
{
  VisualizableVector vecc (new std::vector<int>);

    vecc->push_back(30);            // ok       // one ->
  //vecc.operator->().push_back(30);// error    // one ->

  //vecc->->push_back(30);          // error    // two ->
  vecc.operator->()->push_back(30); // ok       // two ->

  auto proxy3 = vecc.operator->();      // 1st operator->()
  auto pointer = proxy3.operator->();   // 2nd operator->()
  pointer->push_back(30);               // 3rd operator->()      
  return 0;
}

Page 327: Working Draft, Standard for Programming Language C++ 2014-11-19

13.5.6 Class member access [over.ref] 1 operator-> shall be a non-static member function taking no parameters. It implements the class member access syntax that uses ->. postfix-expression -> templateopt id-expression postfix-expression -> pseudo-destructor-name An expression x->m is interpreted as (x.operator->())->m for a class object x of type T if T::operator->() exists and if the operator is selected as the best match function by the overload resolution mechanism (13.3).

I.e. x->m is (x.operator->())->m.


Solution

  • a->b is defined as (*a).b if and only if a is a pointer.

    If a is not a pointer, it is defined as (a.operator->())->b. Now typically operator-> returns a pointer, so it then does a (*(a.operator->())).b and done.

    But if it instead returns a non-pointer, this definition is recursive.

    There is no similar recursive definition for unary operator*.

    In short, the standard says so. Why? Because the writers thought it would be both elegant and useful.

    As an aside, there is an active proposal for operator. which will probably be in C++ as of 2021. This would permit (*a).b to behave the same as a->b.