c++ deleted move assignment operator compilation issues - c++

The following code fails with gcc 4.8.0 (mingw-w64) with -O2 -std=c++11 -frtti -fexceptions -mthreads
#include <string>
class Param
{
public:
Param() : data(new std::string) { }
Param(const std::string & other) : data(new std::string(other)) { }
Param(const Param & other) : data(new std::string(*other.data)) { }
Param & operator=(const Param & other) {
*data = *other.data; return *this;
}
~Param() {
delete data;
}
Param & operator=(Param &&) = delete;
private:
std::string * data;
};
int main()
{
Param param;
param = Param("hop");
return 0;
}
With the error : error: use of deleted function 'Param& Param::operator=(Param&&)'
On the line :
param = Param("hop");
And compiles well if I remove the move assignment delete line.
There should be no default move assignment operator since there are user defined copy constructors, user defined copy assignment, and destructors, so deleting it should not affect the compilation, why is it failing?
And why is the allocation simply not using a copy assignment?

The function you deleted is exactly the assignment operator you try to use in main. By explicitly defining it as deleted you declare it and at the same time say using it is an error. So when you try to assign from an rvalue (Param("hop")), the compiler first looks whether a move assignment operator was declared. Since it was and is the best match, it tries to use it, just to find that it was deleted. Thus the error.
Here's another example of this mechanism which uses no special functions:
class X
{
void f(int) {}
void f(short) = delete;
};
int main()
{
X x;
short s;
x.f(s); // error: f(short) is deleted.
}
Removing the deleted f(short) will cause the compiler to select the non-deleted f(int) and thus compile without error.

Related

copy assignment operator implicitly deleted because field has a deleted copy assignment operator

I'm getting this error as soon as I define a destructor, but without that the compilation succeed, but I badly want to define destructor to debug some seg faults.
class Socket {
private:
seastar::output_stream<char> socket;
public:
std::string peer;
public:
Socket() = default;
template <typename... Args>
explicit Socket(seastar::output_stream<char> &&s, std::string p) : socket(std::move(s)), peer(p) {}
~Socket() {
std::cout << "Socket has gone out of scope" << "\n";
}
seastar::future<> send(std::string message) {
co_await socket.write(message);
co_await socket.flush();
}
seastar::future<> close() {
co_await socket.close();
}
};
Compilation fails with,
error: object of type 'Socket' cannot be assigned because its copy assignment operator is implicitly deleted
connection->second.socketObj = std::move(socketObj);
^
./socketwrapper.h:44:46: note: copy assignment operator of 'Socket' is implicitly deleted because field 'socket' has a deleted copy assignment operator
seastar::output_stream<char> socket;
Is there anyway to fix this issue?
The underlying question here is "Why is the compiler trying to use a copy-assignment when I'm explicitly using std::move()?"
So let's see a simple, well-formed example:
struct MyStruct {
MyStruct()
: some_data(new int(12)) {}
MyStruct(const MyStruct& other)
: some_data(new int(*other.some_data)) {}
MyStruct& operator=(const MyStruct& rhs) {
delete some_data;
some_data = new int(*rhs.some_data);
return *this;
}
~MyStruct() {
delete some_data;
}
int * some_data;
};
MyStruct foo() {
return MyStruct();
}
int main() {
MyStruct a;
a = foo(); // <--- copy-assignment here, no choice.
}
It's pretty obvious that's it's important for the copy-assignment to be used, despite it being a RValue scenario.
But why is that program well-formed in the first place? It's a move-assignment setup and I have no move-assignment operator. Can't the compiler just give me a slap on the fingers? The thing is, this was well-formed before C++11 and move-semantics were a thing, so it must keep behaving correctly.
So, in the presence of any other constructors/destructor/assignment-operator, if there is no move assignment/constructor present, the copy assignment/copy-constructor must be used instead, just in case it happens to be code that was written a long time ago.
The immediate fix for you is to add a move-assignment operator. At that point, it might also help to clean things up and complete the full rule-of-5.
class Socket {
private:
seastar::output_stream<char> socket;
public:
std::string peer;
public:
Socket() = default;
explicit Socket(seastar::output_stream<char> &&s, std::string p)
: socket(std::move(s)), peer(p) {}
Socket(Socket&&) = default;
Socket& operator=(Socket&&) = default;
// These are technically redundant, but a reader wouldn't know it,
// so it's nice to be explicit.
Socket(const Socket&) = delete;
Socket& operator=(const Socket&) = delete;
// ...
Add
Socket(Socket&&)=default;
Socket& operator=(Socket&&)=default;

Move constructor needed, but not used, in array initialization with elements of class with deleted copy constructor

I apologize for the wordy title, but I'm having trouble concisely expressing this question.
I have a class with a deleted copy constructor and copy assignment operator. When I attempt to initialize an array with instances of the class I get a compiler error unless I provide a move constructor. However, the provided move constructor is not actually invoked.
Here's a MWE illustrating the issue.
#include <cstdio>
#include <string>
class A
{
public:
explicit A(std::string s) : s_(s) { puts("constructor"); }
A() = delete;
~A() = default;
A(const A &other) = delete;
A &operator=(const A &other) = delete;
// A(A &&other) : s_(std::move(other.s_)) { puts("move constructor"); }
void print_s() { printf("%s\n", s_.c_str()); }
private:
std::string s_;
};
int main()
{
A arr[]{A{"some"}, A{"string"}};
for (auto &a : arr) {
a.print_s();
}
}
With the move constructor commented out as shown, I get the error:
> g++ file.cc -g -std=c++11 -o file && ./file
file.cc: In function ‘int main()’:
file.cc:22:32: error: use of deleted function ‘A::A(const A&)’
22 | A arr[]{A{"some"}, A{"string"}};
| ^
file.cc:10:2: note: declared here
10 | A(const A &other) = delete;
| ^
file.cc:22:32: error: use of deleted function ‘A::A(const A&)’
22 | A arr[]{A{"some"}, A{"string"}};
| ^
file.cc:10:2: note: declared here
10 | A(const A &other) = delete;
| ^
If I uncomment the move constructor I get the output
constructor
constructor
some
string
So, the move constructor does not actually appear to be invoked.
Why is the move constructor needed? Have I made a mistake somewhere (e.g., a poorly implemented move constructor)?
With
A arr[]{A{"some"}, A{"string"}};
You are creating two temporary A objects and then copying/moving them into the array. Now, the compiler can optimize this copy/move away, but that class still needs to be copyable/moveable in order for this to happen. When you comment out your move constructor, the class is no longer copyable or moveble, so you get a compiler error.
It should be noted that this changed in C++17 with its guaranteed copy elision. The code will compile in C++17 and beyond for types that can't be copied or moved.
If you change
explicit A(std::string s) : s_(s) { puts("constructor"); }
to
A(std::string s) : s_(s) { puts("constructor"); }
then your main function could become
int main()
{
A arr[]{{"some"}, {"string"}};
for (auto &a : arr) {
a.print_s();
}
}
which will compile even if you can't copy or move an A.

why move-only object can not add explicit keyword?

test.cpp:
#include <iostream>
class MacroObject {
public :
MacroObject() = default;
MacroObject(const MacroObject&) = delete ;
MacroObject& operator=(const MacroObject&) = delete;
explicit MacroObject(MacroObject&&) = default;
MacroObject& operator=(MacroObject&&) = default;
int init(){return 0;}
int get(){return 0;}
};
MacroObject getObj(){
MacroObject obj;
obj.init();
return obj;
}
int main(){
MacroObject obj{getObj()};
std::cout << obj.get() << std::endl;
return 0;
}
I use this command with g++ 4.8.5:
g++ -std=c++11 test.cpp
I get this error message:
test.cpp: In function 'MacroObject getObj()':
test.cpp:19:8: error: use of deleted function 'MacroObject::MacroObject(const MacroObject&)'
return obj;
^
test.cpp:6:1: error: declared here
MacroObject(const MacroObject&) = delete ;
^
When I remove explicit, it is ok.
Why g++ uses the deleted copy-constructor and not the move-constructor?
By making the move constructor explicit you can't do
MacroObject x;
MacroObject y = std::move(x);
but can do
MacroObject y(std::move(x));
The return from the function, even in newer C++ versions where mandatory copy/move elision (NRVO) is in effect, tries to match the top version. Since that isn't a match, it checks the copy constructor. Since that is deleted, it stops with a failure.
It does not go on to try explicit versions. They are not candidates.
Making the copy and move constructors explicit makes the class useless with many standard library classes / functions so I recommend not making them explicit.

E1776 upon assignment (overloaded) with implicit cast access

Suppose I have the code below, where the copy-assignment operator is deleted and an int-assignment operator is emplaced alongside an int-access operator (not marked with the explicit keyword). The assignment of b to a only works when explicitly casting to int as below, while a simple a = b; generates a compilation error of E1776 function "OverloadTest::operator=(const OverloadTest &)" cannot be referenced -- it is a deleted function. Is there any explanation for this behavior, which ought to take advantage of the explicit deletion and the implementation of implicit operators? Using MSVC++ 14.15.
class OverloadTest
{
int i;
public:
OverloadTest(int i) : i(i)
{
}
OverloadTest operator=(const OverloadTest &) = delete;
int operator=(const int &other)
{
i = other;
return i;
}
operator int()
{
return i;
}
};
int main()
{
OverloadTest a(1), b(2);
a = b; // E1776
a = (int)b; // OK
int (OverloadTest::* e)(const int &) = &OverloadTest::operator=;
(a.*(&OverloadTest::operator=))(b); // E0299
(a.*e)(b); // OK
return 0;
}
Actually it is not really clear why you expected something else as this is just how deleting a method is supposed to work. From cppreference (emphasize mine):
If, instead of a function body, the special syntax = delete ; is used,
the function is defined as deleted. Any use of a deleted function
is ill-formed (the program will not compile).
By writing
OverloadTest operator=(const OverloadTest &) = delete;
you do define the operator, but calling it will make your code ill-formed. I find it difficult to answer more than that, because your example is rather academic. You can make a=b; work if you simple do not declare the operator=(const OverloadTest&) at all. However, note that then the compiler generated operator= will be used to evaluate a=b;. Though as your class only has the int member you actually cannot tell the difference between calling that operator or your conversion followed by operator=(int). Hope that helps.

Objects that can be initialized but not assigned

I need to create a class whose objects can be initialized but not assigned.
I thought maybe I could do this by not defining the assignment operator, but the compiler uses the constructor to do the assignment.
I need it to be this way:
Object a=1; // OK
a=1; // Error
How can I do it?
Making a const will do the trick
const Object a=1; // OK
Now you won't be able to assign any value to a as a is declared as const. Note that if you declare a as const, it is necessary to initialize a at the time of declaration.
Once you have declared a as const and also initialized it, you won't be able to assign any other value to a
a=1; //error
You can delete the assignment operator:
#include <iostream>
using namespace std;
struct Object
{
Object(int) {}
Object& operator=(int) = delete;
};
int main()
{
Object a=1; // OK
a=1; // Error
}
Alternative Solution
You can use the explicit keyword:
#include <iostream>
using namespace std;
struct Object
{
explicit Object(int) {}
};
int main()
{
Object a(1); // OK - Uses explicit constructor
a=1; // Error
}
Update
As mentioned by user2079303 in the comments:
It might be worth mentioning that the alternative solution does not prevent regular copy/move assignment like a=Object(1)
This can be avoided by using: Object& operator=(const Object&) = delete;
I hoped this would be so by not defining the assignment operator
This doesn't work because the copy assignment operator (which takes const Object& as parameter) is implicitly generated. And when you write a = 1, the generated copy assignment operator will be tried to invoke, and 1 could be implicitly converted to Object via converting constructor Object::Object(int); then a = 1; works fine.
You can declare the assignment operator taking int as deleted (since C++11) explicitly; which will be selected prior to the copy assignment operator in overload resolution.
If the function is overloaded, overload resolution takes place first, and the program is only ill-formed if the deleted function was selected.
e.g.
struct Object {
Object(int) {}
Object& operator=(int) = delete;
};
There're also some other solutions with side effects. You can declare Object::Object(int) as explicit to prohibit the implicit conversion from int to Object and then make a = 1 fail. But note this will make Object a = 1; fail too because copy initialization doesn't consider explicit constructor. Or you can mark the copy assignment operator deleted too, but this will make the assignment between Objects fail too.
How can I do it?
Option 1:
Make the constructor explicit
struct Object
{
explicit Object(int in) {}
};
Option 2:
delete the assignment operator.
struct Object
{
Object(int in) {}
Object& operator=(int in) = delete;
};
You can use both of the above options.
struct Object
{
explicit Object(int in) {}
Object& operator=(int in) = delete;
};
Option 3:
If you don't want any assignment after initialization, you can delete the assignment operator with Object as argument type.
struct Object
{
explicit Object(int in) {}
Object& operator=(Object const& in) = delete;
};
That will prevent use of:
Object a(1);
a = Object(2); // Error
a = 2; // Error
Deleted functions are available only from C++11 onwards, for older compilers you can make the assignment operator private.
struct Object
{
Object(int) {}
private:
Object& operator=(int);
};
Compiler will now throw error for
Object a=1; //ok
a=2; // error
But you can still do
Object a=1,b=2;
b=a;
Because the default assignment operator is not prevented from being generated by the compiler. So marking default assignment private will solve this issue.
struct Object
{
Object(int) {}
private:
Object& operator=(Object&);
};