synthesized default constructor as deleted - c++

I read this in the book:
The synthesized copy-assignment operator is defined as deleted if a
member has a deleted or inaccessible copy-assignment operator, or if
the class has a const or reference member.
And why we can not use reference type?

you're talking about default constructor (and not reassignment or copy constructor).
const member whose type does not explicitly define a default constructor
It forbids the default constructor, else you will have an uninitialized const value (so useless). (if it was not const, the fact that it was uninitialized is not a problem, we may assign it later).
a reference member that does not have an in-class initializer
It is forbidden also, as reference is similar to a non null const pointer.
struct NoDefaultConstructor
{
// No default constructor can be generated.
const int i; // which value to set by default ?
int& r; // reference which object by default?
};
struct InClassInitializerSoDefaultConstruct
{
// default constructor is generated here.
const int i = 42;
int j;
int& r = j;
};
Edit to answer edited Q
For assignment, const value cannot be changed.
and reference are like non null const pointer.
Note that copy constructor doesn't have this restriction because you may (and have to)
initialize const` value.

Related

Why delete of templete copy constructor cause assignment operator disfunctional?

I have code like below which looks a little bit Confusing. I define a template class. It has a user-defined constructor. When I declare two objects of this template class by "operator =", its user-defined contructor is called to my surprise. Besides, After deleting its copy constructor, the compiling cannot even pass during resolution of "operator =". Does templete constructors have different rules than non-templete class?
#include "iostream"
using namespace std;
template <typename T>
class MyPtr
{
private:
T p_;
public:
MyPtr(T P = NULL): p_(P)
{
cout<<"track!!"<<endl;
}
//MyPtr(const MyPtr<T> & P) = delete;
~MyPtr()
{
}
};
int main()
{
int i=3;
int j=4;
MyPtr<int> p = i;
MyPtr<int> pp = j;
}
Does templete constructors have different rules than non-templete class?
No.
MyPtr<int> p = i; (and MyPtr<int> pp = j;) is copy initialization. Note that this is initialization but not assignment, as the effect p is initialized by the constructor MyPtr::MyPtr(T). e.g.
MyPtr<int> p = i; // initialization; constructors get called
p = j; // assignment; assignment operators (operator=) get called
Before C++17, i would be converted to MyPtr<int> via MyPtr::MyPtr(T) firstly, then the conversion result, i.e. the temporary MyPtr<int> is copied/moved to initialize p; even the copy/move operation is allowd to be optimized, the copy/move constructor is required to be accessible. Declaring copy constructor as delete makes copy constructor unusable and move constructor won't be generated, thus makes MyPtr<int> p = i; ill-formed until C++17.
Since C++17 the optimization is mandatory and the copy/move constructor is not required to be accessible again, that means your code would compile fine with C++17 mode even declaring copy constructor as delete.
If T is a class type, and the cv-unqualified version of the type of other is not T or derived from T, or if T is non-class type, but the type of other is a class type, user-defined conversion sequences that can convert from the type of other to T (or to a type derived from T if T is a class type and a conversion function is available) are examined and the best one is selected through overload resolution. The result of the conversion, which is a prvalue temporary (until C++17) prvalue expression (since C++17) if a converting constructor was used, is then used to direct-initialize the object. The last step is usually optimized out and the result of the conversion is constructed directly in the memory allocated for the target object, but the appropriate constructor (move or copy) is required to be accessible even though it's not used. (until C++17)

Why synthesized copy-assignment operator is defined as deleted if the class has a reference member?

In C++ Primer, Fifth Edition, §13.1.6:
The synthesized copy-assignment operator is defined as deleted if a member has a deleted or inaccessible copy-assignment operator, or if the class has a const or reference member.
The explanation from the chapter:
Although we can assign a new value to a reference, doing so changes the value of the object to which the reference refers. If the copy-assignment operator were synthesized for such classes, the left-hand operand would continue to refer to the same object as it did before the assignment. It would not refer to the same object as the right-hand operand. Because this behavior is unlikely to be desired, the synthesized copy-assignment operator is defined as deleted if the class has a reference member.
Copying the class changes the object to which the reference member refers. Isn't this desired? Why the explanation say "is unlikely to be desired"?
Concretely,
class A {
public:
A(int &n) : a(n) {}
private:
int &a;
};
int main() {
int n = 1;
A a(n);
/* Why is this allowed? */
A b(a);
/*
Why is this not allowed?
error C2280: 'A &A::operator =(const A &)': attempting to reference a deleted function
*/
b = a;
return 0;
}
A reference can't be reassigned once it's created. That means it's impossible to make a proper assignment operator if the class contains a reference member.
A copy constructor is a different matter, because the reference can be assigned at object creation.

Why we need to use initializer list while initializing reference members? [duplicate]

As self-exercise, I have written this simple code:
#include <iostream>
int gIndex = 3;
template <class T> class Array
{
public:
explicit Array(int size);
T& operator[](int i) { return m_data[i]; }
T operator[](int i) const { return m_data[i]; }
T getAnchorPoint() const { return m_data[m_anchor]; }
private:
T* m_data;
int m_size;
int& m_anchor;
};
template <class T> Array<T>::Array(int size) : m_size(size), m_data(new T[size])
{
memset(m_data, 0, size*sizeof(T));
m_anchor = gIndex;
}
int main()
{
Array<double> a(10);
return 0;
}
I got a compilation error , which says:
error C2758: 'Array<T>::m_anchor' : must be initialized in constructor base/member initializer list
It has never happened , what brings me to ask this question:
Must any class-member reference type be initialized in the constructor initialization list?
If so, why? Is that related somehow to the fact that a reference type can never be reassigned?
Are there more types that must be initialized in constructor initialization list?
Does any class-member reference type must be initialized in the constructor initialization list?
Yes.
If so, why? Is that related somehow to the fact the a reference type can never be reassigned?
That's part of the reason. The other part is because a reference must be initialized, and it has no default constructor.
Are there more types that must be initialized in constructor initialization list?
Any type that doesn't have an assignment operator (be it copy or move) or default constructor. This obviously includes (but is not limited to) const members as they can't be modified once they've been constructed.
As a rule of thumb, you should (almost) always prefer to initialize your members in the constructor's initialization list: why waste cycles first default-constructing an object and then only assigning to it (if this is even possible), when you could construct it correctly in the first place?
Must any class-member reference type be initialized in the constructor initialization list?
Yes.
Is that related somehow to the fact that a reference type can never be reassigned?
Yes, plus the fact that there is no "null" or default construction for a reference. It is an alias for another object, and it has to be bound to it from the outset. You cannot do this (this is not inside a class definition):
int main()
{
int& iref; // ERROR
int i = 42;
int& iref2 = i; // OK
}
because iref must alias something.
Must any class-member reference type be initialized in the constructor initialization list?
Yes, we should always use initializer list to initialize reference members of a class.
If so, why? Is that related somehow to the fact that a reference type can never be reassigned?
A constructor has two phases namely initialization and computation. So, even if you don't use initializer list for a data member compiler will initialize it with random value. And in in computation phase which typically starts with the '{' of constructor body, if you do an assignment then compiler will complain as the reference member was already intialized. So, your only chance to initialize such member is the constructor initializer list.
Are there more types that must be initialized in constructor initialization list?
Yes, const data members are the other type for which you need an initializer list.

Why It is illegal to copy an object if a member of the class is a reference?

I met a quiz saying that the code in line 18 below is ill-formed because "It is ill-formed to use an implicitly defined assignment operator when one of the members that will need to be copied is a reference. "
I couldn't understand that. Why reference could not be copied? Why Line 16 is legal? Line 16 is quite similar to line 18, a copy constructor still need to do the copy, right?
1 #include <iostream>
2
3 struct A
4 {
5 A(int& var) : r(var) {}
6
7 int &r;
8 };
9
10 int main(int argc, char** argv)
11 {
12 int x = 23;
13
14 A a1(x);
15
16 A a2 = a1;
17
18 a2 = a1;
19
20 return 0;
21 }
Line 16 uses a copy constructor, line 18 uses the assignment operator operator=. Two different functions with different restrictions.
Because a reference can't be rebound, the compiler can't generate an implicit assignment operator that makes any sense. Thus it refuses to do so, and generates an error.
A copy constructor is generating the object for the first time, so it can bind that reference the same way you did in your own constructor.
A class with a reference member has no default-provided copy/move assignment operators. References cannot be rebound to reference a different variable once binding is established. In short, the copy constructor is making that initial establishment, while the default assignment operator would be trying to change it after-binding.
The standard therefore calls this case out for both default copy and move assignment operators.
C++11 § 12.8p23
A defaulted copy/move assignment operator for class X is defined as deleted if X has:
a variant member with a non-trivial corresponding assignment operator and X is a union-like class, or
a non-static data member of const non-class type (or array thereof), or
a non-static data member of reference type, or
a non-static data member of class type M (or array thereof) that cannot be copied/moved because overload resolution (13.3), as applied to M’s corresponding assignment operator, results in an ambiguity or a function that is deleted or inaccessible from the defaulted assignment operator, or
a direct or virtual base class B that cannot be copied/moved because overload resolution (13.3), as applied to B’s corresponding assignment operator, results in an ambiguity or a function that is deleted or inaccessible from the defaulted assignment operator, or
for the move assignment operator, a non-static data member or direct base class with a type that does not have a move assignment operator and is not trivially copyable, or any direct or indirect virtual base class.
You can certainly write your own overload.
#include <iostream>
struct A
{
A(int& var) : r(var) {}
int &r;
A& operator=(const A& obj)
{
r = obj.r; // value copied, reference-binding remains the same
return *this;
}
};
int main(int argc, char** argv)
{
int x = 42;
int y = 43;
A a1(x);
A a2(y);
A a3 = a1; // legal. default copy-ctor invoked
a3 = a2; // legal. user-defined copy-assignment invoked
std::cout << x << ',' << y << '\n';
return 0;
}
Output
43,43
But this will not (and cannot) rebind the reference. The overload provided here changes the referenced data; not the references themselves. Such a distinction is important.
Hope this helps.
Because it is illegal in C++ to reassign to a reference.
int &a = some_int;
a = some_other_int; // value copied not reference
a = some_int; // value copied not reference
When you use assignation operator (generated by compiler), it blindly does the copy of objects and thus try to reassign to your reference and hence is invalid.
When you say a2 = a1;, compiler would try to reassign a1.r to a2.r making it fail at compile time because it is ill-formation.
You can think of a reference as an automatically dereferenced constant pointer. So the line a2 = a1; will remain ill-formatted for the same reason as for the class below.
struct A
{
A(int *var) : p(var) {}
int * const p;
};

Strange behaviour with operator[]

Having spent the last couple of days experimenting with C++ operator[] methods, I've come across an anomaly that I can't easily explain. The code below implements a simple test string class, which allows the user to access single characters via its subscript "operator" method. As I would like to differentiate between lvalue and rvalue subscript contexts, the "operator" method returns an instance of the custom "CReference" class as opposed to a standard C++ character reference. Whilst the "CReference" "operator char()" and "operator=(char)" methods appear to handle each rvalue and lvalue context respectively, g++ refuses to compile without the presence of an additional "operator=(CReference&)" method as documented below. Whilst the addition of this method appears to placate some kind of compile-time dependency, it is never actually invoked at run-time during the execution of the program.
As someone who thought they had acquired a fundamental understanding of C++ intricacies, this project has certainly proved to be a humbling experience. If anyone could see their way to enlightening me as to what's going on here, I would be eternally grateful. In the meantime, I'm going to have to pull out the C++ books in order to reconcile the void** between what I know and what I think know.
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
// Class for holding a reference to type char, retuned by the "operator[](int)"
// method of class "TestString". Independent methods permit differentiation
// between lvalue and rvalue contexts.
class CReference
{
private:
char& m_characterReference;
public:
// Construct CReference from char&
CReference(char& m_initialiser)
: m_characterReference(m_initialiser) {}
// Invoked when object is referenced in a rvalue char context.
operator char()
{
return m_characterReference;
}
// Invoked when object is referenced in a lvalue char= context.
char operator=(char c)
{
m_characterReference = c;
return c;
}
// NEVER INVOKED, but will not compile without! WHY???
void operator=(CReference &p_assignator){}
};
// Simple string class which permits the manipulation of single characters
// via its "operator[](int)" method.
class TestString
{
private:
char m_content[23];
public:
// Construct string with test content.
TestString()
{
strcpy(m_content, "This is a test object.");
}
// Return pointer to content.
operator const char*()
{
m_content[22] = 0;
return m_content;
}
// Return reference to indexed character.
CReference operator[](int index)
{
return m_content[index];
}
};
int main(int argc, char *argv[])
{
TestString s1;
// Test both lvalue and rvalue subscript access.
s1[0] = s1[1];
// Print test string.
printf("%s\n", (const char*)s1);
return 0;
}
The line s1[0] = s1[1]; causes the compiler to generate an implicit copy assignment operator for CReference if you didn't declare one yourself. This causes an error because your class has a reference member, which can't be copied.
If you added an assignment operator that takes a parameter of type const CReference&, it would get called by the assignment.
In your code, you declared a copy assignment operator of type void operator=(CReference &p_assignator). This can't be called because the righthand side of the assignment is a temporary object, which can't be bound to a non-const reference. However, the act of declaring this operator causes the compiler not to try to define an implicit copy assignment operator, and therefore avoids the previous compilation error. Since this operator can't be called, the compiler goes for the other assignment operator that takes a parameter of type char.
What's happening is that without the definition of operator=(CReference&),there are two possible overloads of operator=: the implicitly-defined operator=(const CReference&) and your operator=(char). When it tries to compiled s1[0] = s1[1], it tries to find the best match for operator=, which is the implicitly-defined operator=(const CReference&). But, that isn't allowed to be implicitly-defined, because CReference contains a reference member, so you get an error.
Conversely, when you do define operator=(CReference&), there's no longer an implicitly-defined assignment operator. Sections 12.8 clauses 9-10 of the C++03 standard state:
9) A user-declared copy assignment operator X::operator= is a non-static non-template member function of class X with exactly one parameter of type X, X&, const X&, volatile X& or const volatile X&.109)
10) If the class definition does not explicitly declare a copy assignment operator, one is declared implicitly.
The implicitly-declared copy assignment operator for a class X will have the form
X& X::operator=(const X&)
if
— each direct base class B of X has a copy assignment operator whose parameter is of type const B&, const volatile B& or B, and
— for all the nonstatic data members of X that are of a class type M (or array thereof), each such class type
has a copy assignment operator whose parameter is of type const M&, const volatile M& or
M.110)
Otherwise, the implicitly declared copy assignment operator will have the form
X& X::operator=(X&)
So, when it tries to compile s1[0] = s1[1], it has two overload choices: operator=(CReference&) and operator=(char). It choose operator=(char) as the better overload because it can't convert a temporary object (the result of s1[1]) into a non-const reference.
I compiled your program on Ideone by removing the overloaded = and it gives the following error:
prog.cpp: In member function ‘CReference& CReference::operator=(const CReference&)’:
prog.cpp:9: error: non-static reference member ‘char& CReference::m_characterReference’, can't use default assignment operator
prog.cpp: In function ‘int main(int, char**)’:
prog.cpp:70: note: synthesized method ‘CReference& CReference::operator=(const CReference&)’ first required here
As the error points out,
s1[0] = s1[1];
needs the copy assignment operator for CReference class.
You have a reference member variable in your CReference class
char& m_characterReference;
For classes with a reference member, you need to provide your own implementation of = operator as a default = cannot infer what to do with the reference member. So you need to provide your own version of the = operator.
So a little bit of speculation here:
With,
s1[0]=s1[1]
Ideally, for CReference& CReference::operator=(const CReference&) to be invoked the argument should be a const CReference& but the argument s1[1] returns a non constant CReference&, so:
why does the compiler asks for CReference& CReference::operator=(const CReference&) in the code sample link I posted &
Why does the error go away if we provide a = operator with a non constant argument, like CReference& CReference::operator=(CReference&)
As it seems in the absence of an explicit non constant argument copy constructor the compiler does some kind of a optimization and treats the argument of s1[1] as an constant thus asking for the constant argument copy assignment operator.
If a non constant argument copy assignment operator is explicitly provided the compiler doesn't perform its optimization but just uses the one that is already provided.
Instead of returning a CReference, you can return a char&. That would remove the need for the CReference class, which is dangerous for the reasons that I included in my comment above. You can have two forms of the operator[], one returning char and one returning char&.
OK, let's see:
s1[0] = s1[1];
s1[0] returns CReference
s1[1] returns CReference
Of course you need an assignment operator that accepts CReference.
Hence your statement in bold letters:
// NEVER INVOKED, but will not compile without! WHY???
void operator=(CReference &p_assignator){}
is incorrect.
I think your
void operator=(CReference &p_assignator){}
is invoked since
// Test both lvalue and rvalue subscript access.
s1[0] = s1[1];
use the function, as both s1[0] and s1[1] return CReference. and not char.
Also you would like to do something like this
CReference & operator=(CReference &p_assignator){}
to handle
s1[0] = s1[1] = s1[2]; //etc...
UPDATED: a thought
Why we need to provide user-defined copy assignment operator?
Answer: I think the default behavior of the implicit assignment operator is to re-assign the referenced member variable to refer to another location instead of the semantic in m_characterReference = p_assignator.m_characterReference;. Reference variable for us is a syntactic sugar but for the compiler it is a constant pointer. Thus, the default assignment tries to re-assign this constant pointer.
The two functions CReference::operator char() and CReference::operator=(char) do not represent lvalue and rvalue.
As previously mentioned, your main mistake is that you declared the copy assignment operator to return void. Thus, it won't be invoked.
For the compiler to execute "s[0] = s[1];" and based on your code, first, s[x] will be converted to CRefernce anonymous objects. Then the compiler search for operator=(). Since you provide operator=(char), the compiler is "smart" and try to fulfill this assignment. So, it will invoke operator char() first to convert right-hand side (which CReference(s[1]) to char, then invoke operator=(char) function. The expression (s[0] = s[1]) becomes of type char.
You can avoid this conversion by modifying your code as
class CReference
{
private:
char& m_characterReference;
public:
// Construct CReference from char&
CReference(char &m_initialiser)
: m_characterReference(m_initialiser) {}
char& operator = (const CReference &r) {
m_characterReference = r.m_characterReference;
return m_characterReference;
}
};
The assignment operator returns char if you would like to keep the type of "s1[0] = s1[1]" as character. You should not worry about conversion from char to CReference (in case of s1[0] = s1[1] = s1[2]) since 1-arg constructor will handle this conversion.
A simple and clear example that represents lvalue and rvalue is
class Point {
public:
int& x() {return x_value; }
int& y() {return y_value; }
private:
int x_value;
int y_value;
};
int main()
{
Point p1;
p1.x() = 2;
p1.y() = 4;
cout << p1.x() << endl;
cout << p1.y() << endl;
}