c++rvonrvo

How to enforce RVO for operator return value?


How to enforce RVO in the last 3 operators:

#include <iostream>

class Noisy {
    private:
        int m_value;
    public:
        Noisy(int value = 0): m_value(value) 
        {
            std::cout << "Noisy(int)\n";
        }
        Noisy(const Noisy& other): m_value(other.m_value)  
        { 
            std::cout << "Noisy(const Noisy&)\n";
        }
        Noisy(Noisy&& other): m_value(other.m_value)
        { 
            std::cout << "Noisy(Noisy&&)\n";
        }
        //~Noisy() {
        //    std::cout << "dtor\n";
        //}
        Noisy operator+(const Noisy& rhs) &
        {
            std::cout << "+(const Noisy&)&\n";
            return Noisy(m_value + rhs.m_value);
        }
        Noisy operator+(Noisy&& rhs) &
        {
            std::cout << "+(Noisy&&)&\n";
            rhs.m_value += m_value;
            return rhs; //std::move(rhs);
        }
        Noisy operator+(const Noisy& rhs) &&
        {
            std::cout << "+(const Noisy&) &&\n";
            this->m_value += rhs.m_value;
            return *this; //std::move(*this);
        }

        Noisy operator+(Noisy&& rhs) &&
        {
            std::cout << "+(Noisy&&) &&\n";
            this->m_value += rhs.m_value;
            return *this; //std::move(*this);
        }
};

int main()
{
    Noisy a, b, c, d, e, f, g;
    Noisy z = a + b + c + d + e + f + g;

    return 0;
}

The program run output:

Noisy(int)
Noisy(int)
Noisy(int)
Noisy(int)
Noisy(int)
Noisy(int)
Noisy(int)
+(const Noisy&)&
Noisy(int)
+(const Noisy&) &&
Noisy(const Noisy&)
+(const Noisy&) &&
Noisy(const Noisy&)
+(const Noisy&) &&
Noisy(const Noisy&)
+(const Noisy&) &&
Noisy(const Noisy&)
+(const Noisy&) &&
Noisy(const Noisy&) 

or when explicitly using std::move in the last three operators:

Noisy(int)
Noisy(int)
Noisy(int)
Noisy(int)
Noisy(int)
Noisy(int)
Noisy(int)
+(const Noisy&)&
Noisy(int)
+(const Noisy&) && 
Noisy(Noisy&&)
+(const Noisy&) &&
Noisy(Noisy&&)
+(const Noisy&) &&
Noisy(Noisy&&)
+(const Noisy&) &&
Noisy(Noisy&&)
+(const Noisy&) &&
Noisy(Noisy&&) 

I want no copying in the operators, something like this:

Noisy(int)
Noisy(int)
Noisy(int)
Noisy(int)
Noisy(int)
Noisy(int)
Noisy(int)
+(const Noisy&)&
Noisy(int)
+(const Noisy&) &&
+(const Noisy&) &&
+(const Noisy&) &&
+(const Noisy&) &&
+(const Noisy&) &&

The only way i figured so far is to return a reference from the operators but this clearly would result in a dangling reference.

I compile in c++14 and c++17 with fresh g++.

Update

I understood that it is not possible to force the compiler to do what i want without breaking rules.
But what ptevents the compiler to optimize rvalues locally? I imagine it can create a single rvalue during the first addition which is modified in the next additions and then assigned to the result variable.


Solution

  • Short Answer

    Make your rvalue-reference-caller + const-reference argument operator return an rvalue-reference that is std::moveed out of *this.

    Noisy&& operator+(const Noisy& rhs) &&
    {
        std::cout << "+(const Noisy&)&&\n";
        m_value += rhs.m_value;
        return std::move(*this);
    }
    

    Since this is being fired from an rvalue-reference, you can freely abuse, and move, *this down to the caller where they are free to do whatever they please with it.

    Example

    #include <iostream>
    
    class Noisy {
    private:
        int m_value;
    public:
        Noisy(int value = 0) : m_value(value)
        {
            std::cout << "Noisy()\n";
        }
        Noisy(const Noisy& other) : m_value(other.m_value)
        {
            std::cout << "Noisy(const Noisy&)\n";
        }
        Noisy(Noisy&& other) : m_value(other.m_value)
        {
            std::cout << "Noisy(Noisy&&)\n";
            other.m_value = -1;
        }
    
        ~Noisy()
        {
            std::cout << "~Noisy() : " << m_value << '\n';
        }
    
        Noisy operator+(const Noisy& rhs) const &
        {
            std::cout << "+(const Noisy&)&\n";
            return Noisy(m_value + rhs.m_value);
        }
    
        Noisy&& operator+(const Noisy& rhs) &&
        {
            std::cout << "+(const Noisy&)&&\n";
            m_value += rhs.m_value;
            return std::move(*this);
        }
    
        Noisy operator+(Noisy&& rhs) const
        {
            std::cout << "+(Noisy&&)\n";
            rhs.m_value += m_value;
            return std::move(rhs);
        }
    };
    
    int main()
    {
        Noisy a, b, c, d, e, f, g;
        std::cout << "========================\n";
    
        Noisy z = a + b + c + d + e + f + g;
        std::cout << "========================\n";
    
        return 0;
    }
    

    Output

    Noisy()
    Noisy()
    Noisy()
    Noisy()
    Noisy()
    Noisy()
    Noisy()
    ========================
    +(const Noisy&)&
    Noisy()
    +(const Noisy&)&&
    +(const Noisy&)&&
    +(const Noisy&)&&
    +(const Noisy&)&&
    +(const Noisy&)&&
    Noisy(Noisy&&)
    ~Noisy() : -1
    ========================
    ~Noisy() : 0
    ~Noisy() : 0
    ~Noisy() : 0
    ~Noisy() : 0
    ~Noisy() : 0
    ~Noisy() : 0
    ~Noisy() : 0
    ~Noisy() : 0
    

    Notice after the initial construction of a.operator+(b), the resulting object is propagated by rvalue-reference down the chain. The last operation is the move-construction into z. (thus the -1 in the destructor report).


    I had prepared a long description of how your operator chaining makes this work that I was going to post, but in the end it got rather cluttered. Suffice it to say that this:

    Noisy z = a.operator+(b).operator+(c).operator+(....
    

    is what you're doing, and you need to get the lhs operand of everything past (b) in the chain to push its rvalue-reference to the next call. The operator I showed will allow that.

    Best of luck, I hope I understood your goal.