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.
Related
Consider:
std::shared_ptr<Res> ptr=new Res();
The above statement doesn't work. The compiler complains there isn't any viable conversion....
While the below works
std::shared_ptr<Res> ptr{new Res()} ;
How is it possible?!
Actually, what are the main differences in the constructor{uniform, parentheses, assignments}?
The constructor of std::shared_ptr taking a raw pointer is marked as explicit; that's why = does not allow you to invoke this constructor.
Using std::shared_ptr<Res> ptr{new Res()}; is syntax that allows you to call the an explicit constructor to initialize the variable.
std::shared_ptr<Res> ptr=new Res();
would invoke an implicit constructor taking a pointer to Res as single parameter, but not an explicit one.
Here's a simplified example using a custom class with no assignment operators and just a single constructor:
class Test
{
public:
Test(int i) { }
// mark all automatically generated constructors and assignment operators as deleted
Test(Test&&) = delete;
Test& operator=(Test&&) = delete;
};
int main()
{
Test t = 1;
}
Now change the constructor to
explicit Test(int i) { }
and the main function will no longer compile; the following alternatives would still be viable:
Test t(1);
Test t2{1};
In the following code, to my surprise, the { } appears to create a default initialized instance of int rather than of A.
struct A {
int i = 1;
A() = default;
A& operator=(const A& a) {
this->i = a.i;
return *this;
}
A& operator=(int i) {
this->i = i;
return *this;
}
};
int main() {
A a;
a = { };
return a.i;
}
What rule is used to parse a = { }? I thought that a = { } would have to be equivalent to a = decltype(a){ }.
What is the right term for the type of initialization that takes place here?
Here's an example on godbolt. At -O0 the assembly shows exactly which function is being used. Unless the int assignment operator is removed, A::operator=(int) is called.
Firstly, the braced-init-list {} could be used to initialize both int and A, which are also considered as conversion, and the conversion from {} to int wins in overload resolution, since the conversion from {} to A (via A::A()) is considered as a user-defined conversion.
Assignment operator is not special, compilers just collect all the viable operator=s in overload set, then determine which one should be selected by overload resolution. If you add another operator=(float) you'll get an ambiguous error since conversion from {} to int and from {} to float are both considered as standard conversion with same rank, which wins against user-defined conversion.
Consider the following code :
template <class T>
T average(T *atArray, int nNumValues)
{
T tSum = 0;
for (int nCount = 0; nCount < nNumValues; nCount++)
{
tSum += atArray[nCount];
}
tSum = tSum / nNumValues;
return tSum;
}
And the following question on it :
Which of the following statements about the class/type T must be true in order for the code to compile and run without crashing?
It must be some kind of numeric type
Must have < operator-defined
Must have the [ ] access operator-defined
Must have copy constructor and assignment operator
My Thoughts:
I think it could be something apart from numeric type but it will need to have the operator + and / well defined.
point 2 seems incorrect as < has no relation with / and + operator
same with point 3 and point 4.
Although I am not sure about my reasoning above.
No - It could for example do implicit conversions to/from a numeric type and have operator+= defined.
No - That operator is not used on a T
No - That operator is not used on a T, only a T* and pointer types all have this operator defined.
No - The copy constructor can be deleted and the question says "and" so the answer is no. It needs some kind of assignment operator though.
Consider this class, especially constructed to be able to answer no to as many questions as possible:
struct foo {
foo() = default;
foo(int) {} // implicit conversion from int
foo(const foo&) = delete; // no copy constructor
foo(foo&&) = default; // but move constructor
foo& operator=(const foo&) = delete; // no copy assignment
foo& operator=(foo&&) = delete; // no move assignment
foo& operator=(int) { return *this; }; // but assignment from int
foo& operator+=(const foo&) { return *this; } // for tSum += atArray[nCount];
operator int() const { return 1; } // implicit conversion to int
};
And it would work fine with the function template:
int main() {
foo arr[2];
auto x = average(arr, 2);
}
A class type with sufficient overloaded operators (like std::complex<double>) could work.
The < is only used on int expressions, not involving T.
The atArray[nCount] expression uses a T* pointer, not an expression with type T, so it has the built-in meaning.
Technically no, since fundamental types don't have constructors or assignment operators at all, just similar semantics. Also, technically every class type "has" a copy constructor and assignment operator, even if deleted. Another issue: T could be a class with normal assignment operator, deleted copy constructor, and public non-deleted move constructor.
Probably the intended answer is #4.
The actual requirements on T are:
A variable of type T can be copy-initialized from an int, or from a null pointer constant. (0 is a special expression which can do two different things...)
T is a numeric type, or an enumeration or class type with valid operator functions supporting expressions:
a += b and a = b, where a and b are lvalues of type T
a / n, where a is an lvalue of type T and n is an lvalue of type int
T is move-constructible.
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&);
};
I am trying to lean move semantics and I wrote this example. I would like to move a temporary r-value into an object on stack.
class MemoryPage
{
public:
size_t size;
MemoryPage():size(0){
}
MemoryPage& operator= (MemoryPage&& mp_){
std::cout << "2" <<std::endl;
size = mp_.size;
return *this;
}
};
MemoryPage getMemPage()
{
MemoryPage mp;
mp.size = 4;
return mp;
}
int main() {
MemoryPage mp;
mp = getMemPage();
std::cout << mp.size;
return 0;
}
I get this error at the return of getMemPage():
error: use of deleted function 'constexpr MemoryPage::MemoryPage(const MemoryPage&)'
The copy constructor is:
[...] defined as deleted if any of the following conditions are true:
T has a user-defined move constructor or move assignment operator
In order to solve the immediate problem, you simply provide a copy constructor, i.e.:
MemoryPage(const MemoryPage&) { }
However, as noted in the comments, it's a good idea to consult Rule-of-Three becomes Rule-of-Five with C++11? . In particular, this paragraph summarizes what issues you may run into if you neglect to provide any of the special member functions:
Note that:
move constructor and move assignment operator won't be generated for a class that explicitly declares any of the other special member
functions
copy constructor and copy assignment operator won't be generated for a class that explicitly declares a move constructor or move assignment
operator
a class with a explicitly declared destructor and implicitly defined copy constructor or implicitly defined copy assignment operator is
considered deprecated.
formatted for readability
Therefore it is good practice when writing a class that deals with memory management to provide all five special member functions, i.e.:
class C {
C(const C&) = default;
C(C&&) = default;
C& operator=(const C&) & = default;
C& operator=(C&&) & = default;
virtual ~C() { }
};