Search code examples
design-patternsc++11rvalue-referenceproxy-classes

Does C++11's && (R-value reference) operator obsolete the 'proxy-object' design-pattern?


Item 30 of Scott Meyers' "more effective C++" maps out a 'proxy object' programming design-pattern.

The problem is if you have:

X x;
x[3]=42; 
cout<<x[3]

... you need X's operator[] overload to be able to distinguish between L-value and R-value use.

(maybe you need different code to run,e.g. in the first case heavy copying might be involved, but in the second case maybe we can just pass back a reference).

The proxy pattern is that X contains a Proxy class, and X's operator[] overloads return an object of type Proxy.

So that code becomes:

X x;
{some Proxy object}=42; 
cout<<{some Proxy object}

Now we just have to give our Proxy object an override for "operator=", which handles the first case, and an override for 'conversion to std::string or char*' which handles the second.

And C++'s attempt to find an appropriate type conversion will trigger the relevant override.

However, this book was written before C++11, and one of the main features of C++11 is a new && (R-value reference) operator.

Is there now a simpler way of coding separate R-value and L-value assignments?

Is this proxy-object design-pattern now obsolete?


Solution

  • No, I don't believe there is yet an alternative mechanism here.

    While ref-qualified methods can provide different behavior in certain scenarios, the overloads are resolved based on the status of the object they were invoked upon. They are not resolved on the usage of the object that they return.

    In the following live example, I create a wrapper around a std::vector, attempting to auto-grow the vector when assigning into an index and allowing undefined behavior when merely reading from an index.

    But it doesn't work that way:

    template <typename T>
    struct AutoVector
    {
        std::vector<T> m_vec;
        AutoVector() { m_vec.resize(1); }
    
        T& operator[](const size_t index) &
        {
            std::cout << "Inside operator[" << index << "]&\n";
            if (m_vec.size() < index)
                m_vec.resize(index);
            return m_vec[index];
        }
    
        T operator[](const size_t index) &&
        {
            std::cout << "Inside operator[" << index << "]&&\n";
            return m_vec[index];
        }
    };
    

    If this is invoked the following ways, both will invoke the lvalue-qualified operator[] &:

    AutoVector<int> avec;
    avec[4] = 6;
    std::cout << avec[4] << "\n";
    
        --> Inside operator[4]&
        --> Inside operator[4]&
        --> 6
    

    If this is invoked on a temporary, it can invoke the rvalue-qualified operator[] &&:

    std::cout << AutoVector<int>()[0] << "\n";
    
        --> Inside operator[0]&&
        --> 0
    

    This does not have the desired behavior. Applying the same sort of test to a proxy object returned by operator[] would typically result in it calling the rvalue-qualified methods in all cases, unless the proxy was captured and named. It would still not reflect how the proxy was used.