C& C::operator=(C rhs);
C& C::operator=(C&& rhs);
However, if the compilers could choose the rvalue reference version over the pass-by-value version for rvalue arguments (i.e. c2 = c1 + c1), it could lead to an efficient copy-swap idiom implementation.
Below is a sample code that demonstrates the problem. The issue is explained by the long comment block inside the main function:
#include <algorithm>
class C
{
int* data;
public:
C() : data(nullptr) { }
C(int data) : data(new int)
{
*(this->data) = data;
}
C(const C& rhs) : data(new int)
{
*data = *(rhs.data);
}
C(C&& rhs) : C()
{
// Move constructor is first creating a default
// object and swapping it with the rvalue reference.
swap(*this, rhs);
}
C& operator=(C rhs)
{
// We let the compiler copy into rhs and we swap the
// current object with this copy. Together with the
// move constructor above, this implements the copy-swap
// idiom. Thanks to the move-constructor above, the
// copy to rhs is not a deep copy if the input is an rvalue
// reference.
swap(*this, rhs);
return *this;
}
// The function below is commented out because it fails compilation
// due to ambiguity with the above function. However, if it worked
// it could have saved us an extra call to the move constructor when
// we make calls such as c2 = c1 + c1 (see operator+ below). If it had
// worked, the temporary created from c1 + c1 would have been directly
// taken by rvalue reference instead of its copy (albeit shallow) being
// created.
/*
C& operator=(C&& rhs)
{
swap(*this, rhs);
return *this;
}
*/
C operator+(const C& rhs)
{
C result(*data + *(rhs.data));
return result;
}
friend void swap(C& lhs, C& rhs);
~C()
{
delete data;
}
};
void swap(C& lhs, C& rhs)
{
std::swap(lhs.data, rhs.data);
}
int main()
{
C c1(7);
C c2;
// The following will first create the "result" inside operator+.
// The return value will then get move-constructed from the result
// (I'm assuming that -fno-elide-constructors option is used). Then
// the "rhs" parameter of operator= will get move-constructed from
// this return temporary.
//
// But if we could overload operator=(C&&), this second move-construction
// could have been avoided as the return temporary could have been directly
// captured by rvalue reference.
//
// Granted that we can implement operator=(const C&) and operator=(C&&)
// to make assignment as efficient as possible for both lvalue and rvalue
// inputs. However, to my knowledge, having operator=(const C&) would
// essentially prevent us from having the copy-swap idiom.
c2 = c1 + c1;
return 0;
}
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/62809333-fd1a-4bde-8805-ccc0578a4284%40isocpp.org.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
I remember reading in SO forums that letting the compiler take care of the copy may allow for a more efficient implementation in certain cases. See the following links:
-- https://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa
-- https://web.archive.org/web/20140113221447/http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/
What they say is that if you let the compiler do the copy, rather than doing it yourself, the compiler may accomplish copy-elision instead of pass-by-value.
But now thinking about this more carefully, given that we have a move-assignment operator, in a case where copy elision could have happened (due to the actual parameter being an rvalue object), our move-assignment operator would kick in avoiding the copy.
So in the presence of a move-assignment operator, doing the copy yourself or letting the compiler do it for us would be equally efficient. Please correct me if this reasoning is wrong.
Let me know if this is a stupid idea or something that is already considered. But to my knowledge, if we have the following operator overloads for a class, compilers will generate an ambiguity error (probably enforced by the standard):
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.
struct Foo {
Foo& operator+=(const Foo&);
};
Foo operator+(const Foo& a, const Foo& b) { Foo R; R += a ; return R; }
Foo&& operator+(Foo&& a, const Foo& b) { a += b; return std::move(a); }
Foo&& operator+(const Foo& a, Foo&& b) { return std::move(b) + a; }
Foo&& operator+(Foo&& a, Foo&& b) { return std::move(a) + b; }
struct Foo {
Foo& operator+=(const Foo&); // unchanged
};
Foo operator+(Foo a, Foo b) { a += b; return a; }
In my experience, values are much easier to deal with, especially when you want to return them.
Consider a simple operator+
If you want to use references and optimize for rvalues, you do:And if you do that, you might end up with dangling references in few cases.
struct Foo {
Foo& operator+=(const Foo&);
};
Foo operator+(const Foo& a, const Foo& b) { Foo R; R += a ; return R; }
Foo&& operator+(Foo&& a, const Foo& b) { a += b; return std::move(a); }
Foo&& operator+(const Foo& a, Foo&& b) { return std::move(b) + a; }
Foo&& operator+(Foo&& a, Foo&& b) { return std::move(a) + b; }
struct Foo {
Foo& operator+=(const Foo&);
};
Foo operator+(Foo a, const Foo& b) { a += b; return a; }
Foo operator+(const Foo& a, Foo&& b) { b += a; return b; }
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
class C
{
};
void f(C a)
{
}
void f(const C& a)
{
}
void f(C&& a)
{
}
int main()
{
f(C());
return 0;
}
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.