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.
Short Answer
Make your rvalue-reference-caller + const-reference argument operator return an rvalue-reference that is std::move
ed 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.