In C++ Templates The Complete Guide in section 5.3 Member Templates it's written:
Note that a template assignment operator doesn't replace the default
assignment operator. For assignments of stacks of the same type, the
default assignment operator is still called.
Is this correct, because when I ran below code:
#include<iostream>
using namespace std;
template<typename T>
class Pair
{
public:
T pair1,pair2;
Pair(T i,T j):pair1(i),pair2(j){}
template<typename T1>Pair<T>& operator=(Pair<T1>&);
};
template<typename T>
template<typename T1>
Pair<T>& Pair<T>::operator=(Pair<T1>& temp)
{
this->pair1 =temp.pair1*10;//At this point
this->pair2=temp.pair2;
return *this;
}
int main()
{
Pair<int>P1(10,20);
Pair<int>P2(1,2);
P2=P1;
cout<<P2.pair1<<' '<<P2.pair2<<endl;
return 1;
}
I got answer 100 20.
It didn't give the default assignment answer.
Is that a typing mistake in C++ Templates the Complete Guide?
C++ Templates: The Complete Guide By David Vandevoorde, Nicolai M.
Josuttis
Publisher : Addison Wesley
Pub Date : November 12, 2002 Pages : 552
The copy assignment operator is indeed implicitly declared and considered by overload resolution.
A user-declared copy assignment operator X::operator= is a
non-static non-template member function of class X [..]. 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 non-static 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.
Otherwise, [..]
As you can see the implicitly-declared copy assignment operator for Pair<int> has one parameter of type Pair<int> const& - note the const in particular! Overload resolution favours non-const references over const ones if both can be bound to the argument, [over.ics.rank]/3:
Two implicit conversion sequences of the same form are
indistinguishable conversion sequences unless one of the following
rules applies:
—
Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence
S2 if
[..]
S1 and S2 are reference bindings (8.5.3), and the types to which the
references refer are the same type except for top-level cv-qualifiers,
and the type to which the reference initialized by S2 refers is more
cv-qualified than the type to which the reference initialized by S1
refers.
The specialization of the template lacks a const in the reference parameter, thus it's a better match and is selected.
The default assignment operator accepts the argument as a const reference: http://en.cppreference.com/w/cpp/language/as_operator.
You have defined a version without const, and your version is better in the context of overload resolution (no conversion required).
Try with the following change:
int main()
{
Pair<int>P1(10,20);
Pair<int>P2(1,2);
const Pair<int>& x = P1;
P2=x;
cout<<P2.pair1<<' '<<P2.pair2<<endl;
return 1;
}
to see the expected result.
Related
Consider the following class:
class example
{
public:
auto & operator =(const example &) = default;
auto & operator =(example &&) = default;
};
Are those declarations considered legal?
auto & operator =(const example &) = default;
auto & operator =(example &&) = default;
Are those declarations considered legal?
No.
[dcl.spec.auto] ... If the declared return type of the function contains a placeholder type, the return type of the function is deduced from non-discarded return statements, if any, in the body of the function ([stmt.if]).
A defaulted function definition does not have a body, which conflicts the quoted rule. There is nothing that the return type could be deduced from and there is no rule that states what that type would be in this case.
operator<=> has an exceptional rule specifying what the return type will be when auto is used as shown in Caleth's answer, but operator= does not have such rule. I see no reason such rule couldn't be introduced to allow auto in defaulted assignment operators.
In C++20, Yes1
Let R be the declared return type of a defaulted three-way
comparison operator function, and let xi be the elements of the
expanded list of subobjects for an object x of type C`.
If R is auto, then let cvi Ri be the type of the expression xi <=> xi. The operator function is defined as deleted if that
expression is not usable or if Ri is not a comparison category type
([cmp.categories.pre]) for any i. The return type is deduced as the
common comparison type (see below) of R0, R1, …, Rn−1.
[class.spaceship/2]
Before C++20, No
A function definition of the form: attribute-specifier-seq opt
decl-specifier-seq opt declarator virt-specifier-seq opt = default ; is called an explicitly-defaulted definition. A function that is
explicitly defaulted shall
be a special member function,
have the same declared function type (except for possibly differing ref-qualifiers and except that in the case of a copy
constructor or copy assignment operator, the parameter type may be
“reference to non-const T”, where T is the name of the member
function's class) as if it had been implicitly declared, and
[dcl.fct.def.default] (emphasis added)
But only <=>. A defaulted == must return bool, and assignment has similar restrictions as previous standards.
if F is an assignment operator, and the return type of T1 differs
from the return type of T2 or T1's parameter type is not a
reference, the program is ill-formed;
[dcl.fct.def.default]
not a language lawyer answer
From my experience compilers don't accept auto return type for defaulted special member functions, so I assume they are indeed not allowed by the standard.
The only exception I know of is the C++20 default three-way comparison operator:
#include <compare>
struct X
{
auto operator<=>(const X&) const = default;
};
My C++ colleagues and I ran into a curious construct:
struct A { int i; };
void foo(A const& a);
int main() {
foo(A() = A{2}); // Legal
}
The A() = A{2} expression completely befuddled us as it appears to be assigning A{2} to a temporary, default-constructed object. But see it in compiler explorer (https://gcc.godbolt.org/z/2LsfSk). It appears to be a legal statement (supported by GCC 9 and Clang 9), as are the following statements:
struct A { int i; };
int main() {
A() = A{2};
auto a = A() = A{3};
}
So it appears, then, that in some contexts A() is an lvalue. Or is something else going on here? Would appreciate some explanation and, preferably, a reference to the C++17 standard.
Update: #Brian found that this is a duplicate of assigning to rvalue: why does this compile?. But would really appreciate if someone could find the appropriate reference in the C++ standard.
A{} is always an rvalue per [expr.type.conv]
1 A simple-type-specifier or typename-specifier followed by a parenthesized optional expression-list or by a braced-init-list (the initializer) constructs a value of the specified type given the initializer.
If the type is a placeholder for a deduced class type, it is replaced by the return type of the function selected by overload resolution for class template deduction for the remainder of this subclause.
2 If the initializer is a parenthesized single expression, the type conversion expression is equivalent to the corresponding cast expression.
Otherwise, if the type is cv void and the initializer is () or {} (after pack expansion, if any), the expression is a prvalue of the specified type that performs no initialization.
Otherwise, the expression is a prvalue of the specified type whose result object is direct-initialized with the initializer.
If the initializer is a parenthesized optional expression-list, the specified type shall not be an array type.
emphasis mine
The reason these works is here is nothing in the standard to stop it from working.
For built in types like int there is [expr.ass]/1
The assignment operator (=) and the compound assignment operators all group right-to-left. All require a modifiable lvalue as their left operand; their result is an lvalue referring to the left operand.
So this stops you from doing int{} = 42;. This section doesn't apply to classes, though. If we look in [class.copy.assign] there is nothing that says that an lvalue is required, but the first paragraph does state
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&
Which means
A{} = A{2};
is actually
A{}.operator=(A{2})
Which is legal to do on an rvalue class object since the default operator = for your class has no ref-qualifier to stop it from being called on rvalues. If you add
A& operator=(const A& a) & { i = a.i; }
to A instead of using the default assignment operator then
A{} = A{2};
would no longer compile since the operator= will only work on lvalues now.
Consider the following code:
#include <iostream>
using namespace std;
class A{
private:
int a;
public:
A(int);
void print();
void operator =(int);
};
// One argument constructor
A::A(int b){
cout<<"Inside one argument constructor"<<endl;
this->a=b;
}
void A:: operator =(int b){
cout<<"Inside operator function"<<endl;
this->a = b;
}
void A::print(){
cout<<"Value of a ="<<a<<endl;
}
int main() {
/* INITIALIZATION */
A obj1=2;
obj1.print();
/* ASSIGNMENT */
obj1=3;
obj1.print();
return 0;
}
The output of the above code can be seen here: http://ideone.com/0hnZUb . It is:
Inside one argument constructor
Value of a =2
Inside operator function
Value of a =3
So what I have observed is that during initialization, the one-argument constructor is called but during assignment, the overloaded assignment operator function is called. Is this behavior enforced by the C++ standard, or is it compiler specific? Could anyone quote the section from the standard that defines this behavior?
This is standard behavior.
I searched in the latest C++ standard, ISO/IEC 14882:2011(E), Programming Language C++.
The following code
A obj1 = 2;
Is an initialization. It is described in section 8.5 initializers, clause 16.
16 The semantics of initializers are as follows. The destination type is the type of the object or reference being initialized and the source type is the type of the initializer expression. If the initializer is not a single (possibly parenthesized) expression, the source type is not defined.
Following part of this clause is very long. I only reference which related to your sample code.
— If the destination type is a (possibly cv-qualified) class type:
— If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered. The applicable constructors are enumerated (13.3.1.3), and the best one is chosen through overload resolution (13.3). The constructor so selected is called to initialize the object, with the initializer expression or expression-list as its argument(s). If no constructor applies, or the overload esolution is ambiguous, the initialization is ill-formed.
Section 13.3.1.3 is Initialization by constructor.
Accroding to 8.5 and 13.3.1.3, the constructor
A(int);
is selected to initialize obj1.
As for the second one
obj1 = 3;
This behavior is defined by 5.17 Assignment and compound assignment operators, clause 4.
4 If the left operand is of class type, the class shall be complete. Assignment to objects of a class is defined by the copy/move assignment operator (12.8, 13.5.3).
13.5.3 Assignment, clause 1.
1 An assignment operator shall be implemented by a non-static member function with exactly one parameter. Because a copy assignment operator operator= is implicitly declared for a class if not declared by the user (12.8), a base class assignment operator is always hidden by the copy assignment operator of the derived class.
Function
void operator =(int);
is selected for
obj1 = 3;
according to overload rule.
It is standard, and I guess it is not surprising that constructor is for initialisation, and assignment operator is used for assignment. In the C++ standard the section 12.8 - Copying and moving class objects - starts with
A class object can be copied or moved in two ways: by initialization (12.1, 8.5), including for function argument passing (5.2.2) and for function value return (6.6.3); and by assignment (5.17). Conceptually, these two operations are implemented by a copy/move constructor (12.1) and copy/move assignment operator (13.5.3).
I have the following code snippet:
struct T {
T(const T&) = default;
T(const S &);
};
struct S {
operator T();
};
int main() {
S s;
T t = s; // copy-initialization of class type
return 0;
}
My question is why the compiler prefers S::operator T() for the initialization of t rather than reporting an error that the initialization is ambigious. In my opinion the following happens (correct me if i am wrong):
t is copy-initialized with an lvalue of type S
S is not T and S is also not a subclass of T, so S and T are unrelated
because of the fact that the variable t is copy-initialized and the fact that the types S and T are unrelated, the compiler tries to find user-defined-conversion sequences to do the initialization.
overload resolution is responsible for selecting the best user-defined-conversion which can be either a converting constructor of T or the conversion function of S
the implicite conversion sequence for the constructor T::T(const S&) from the argument s is the identity conversion because the lvalue s can be bound directly to this lvalue reference
the implicite conversion sequence for the conversion function S::operator T() from the argument s is also the identity conversion, because the implicit object parameter is S&
Both the constructor and the conversion function return a prvalue of type T which can be used to direct-initialize the variable t. That means that the second standard conversion sequence of both user-defined-conversion sequences is the identity conversion.
This would mean that both user-defined-conversion sequences are equally good. Or is there a special rule which prefers the conversion functions?
I was reading the following rules in the c++11 standard:
The initialization that occurs in the form
T x = a;
as well as in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and aggregate member initialization (8.5.1) is called copy-initialization.
The semantics of initializers are as follows...If the destination type is a (possibly cv-qualified) class type: If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified
version of the source type is the same class as, or a derived class of, the class of the destination,
constructors are considered....
Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated as described in 13.3.1.4, and the best one is chosen through overload resolution (13.3)
User-defined conversion sequence U1 is a better conversion sequence than another user defined conversion sequence U2 if they contain the same user-defined conversion function or constructor and if the second standard conversion sequence of U1 is better than the second standard conversion sequence of U2
Maybe i am making false assumptions. I hope you can help me!
The conversion from S using the conversion operator is better than the conversion to T taking an S const as argument. If you make s an S const, the constructor is preferred: Your identity operation in one case, indeed, is an identity operation, in the other case it isn't. If you make the conversion operator of S a const member, you get an ambiguity. Below is a test program demonstrating all cases:
struct S;
struct T {
T(const T&) = default;
T(const S &);
};
struct S {
S(); // needed to allow creation of a const object
#ifdef AMBIGUOUS
operator T() const;
#else
operator T();
#endif
};
int main() {
#ifdef CONST
S const s;
#else
S s;
#endif
T t = s; // copy-initialization of class type
return 0;
}
I think i found the rule which clarifies this:
13.3.3.2: ... S1 and S2 are reference bindings (8.5.3), and the types to which the references refer are the same type except for top-level cv-qualifiers, and the type to which the reference initialized by S2 refers is more cv-qualified than the type to which the reference initialized by S1 refers.
In the member function S::operator T() the implicit object parameter has type S& which is directly bound to the lvalue s of type S. In the constructor T::T(const S&) the parameter is directly bound to the lvalue s of type S but this reference binding is more cv-qualified than in the operator function, so the operator function is preferred by overload resolution.
Do you agree with this?
This code
struct T {
int m_x;
T(int x) : m_x(x) {}
operator T() {
return T(0);
}
};
int main() {
volatile T v(2);
T nv(1);
nv = v; // nv.m_x = 0
}
Gives:
prog.cpp: In function ‘int main()’:
prog.cpp:14:10: error: no match for ‘operator=’ in ‘nv = v’
prog.cpp:14:10: note: candidates are:
prog.cpp:1:8: note: T& T::operator=(const T&)
prog.cpp:1:8: note: no known conversion for argument 1 from ‘volatile T’ to ‘const T&’
prog.cpp:1:8: note: T& T::operator=(T&&)
prog.cpp:1:8: note: no known conversion for argument 1 from ‘volatile T’ to ‘T&&’
What typecast overload do I need to define for this to work?
The short answer:
Yes you can but the compiler won't do the job for you.
You cannot have an compiler-provided conversion from volatile T to T but a user-defined implicit conversion using a volatile-qualified constructor.
It is also impossible to declare such a conversion by using explicitly-defaulted versions of the special member functions (see long answer for reference).
You'll have to provide a user-defined way of conversion to enable such assignments. You can either
use a non-explicit copy constructor with a cv-qualified argument for implicit user-defined conversion or
a copy assignment operator taking a v-qualified argument.
Example:
X (X const volatile & xo);
X& operator= (X const volatile & xo);
The long answer with standard quotes 'n stuff or
why doesn't the compiler do this for me?
Way 1: User-provided constructor from volatile T
Standard, ISO 14882:2011, 4/3
An expression e can be implicitly converted to a type T if and only if the declaration T t=e; is well-formed, for some invented temporary variable t (8.5).
Since the declaration T t = e;, where in this case e is typed volatile T, requires such a copy-initialization to be valid, you'll need a copy constructor from a volatile T.
I already answered (Why am I not provided with a default copy constructor from a volatile?).
Therefore you'll need to provide a user-defined way of copy-initialization of T from volatile T.
X (X const volatile & xo);
Note:
This is a declaration, you'll also have to provide a definition.
The constructor must not be explicit.
Providing an user-defined copy constructor taking a volatile argument will result in the abscence of an implicitly generated default assignment operator.
This will make your assignment work.
Way 2: User-provided copy assignment operator from volatile T
Another way to make the assignment of your example code work is a copy assignment operator.
Unfortunatelly, the standard also does say that a compiler will not provide implicit copy assignment operators for the conversion of volatile to non volatile objects.
Standard, ISO 14882:2011, 12.8/18
If the class definition does not explicitly declare a copy assignment operator, one is declared implicitly. If the class definition declares a move constructor or move assignment operator, the implicitly declared copy assignment operator is defined as deleted; otherwise, it is defined as defaulted (8.4). The latter case is deprecated if the class has a user-declared copy constructor or a user-declared destructor. 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 non-static 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. 122
Otherwise, the implicitly-declared copy assignment operator will have the form
X& X::operator=(X&)
Note 122 on 12.8/18
the reference parameter of the implicitly-declared copy assignment operator cannot bind to a volatile lvalue; see C.1.9.
In the other answer I quoted C.1.9 where it says:
The implicitly-declared copy constructor and implicitly-declared copy assignment operator cannot make a copy of a volatile lvalue. [ ... ]
The result is that we'll have to provide a suitable copy assignment operator if we want to have one.
X& operator= (X const volatile & xo);
Also note that you cannot declare an assignment/constructor from volatile explicitly-defaulted.
C++11 Standard 8.4.2/1
A function that is explicitly defaulted shall
be a special member function,
have the same declared function type (except for possibly differing ref-qualifiers and except that in the case of a copy constructor or copy assignment operator, the parameter type may be “reference to non-const T”, where T is the name of the member function’s class) as if it had been implicitly declared, and
not have default arguments.
The following Note was removed from the final C++11 standard but was present in the Draft N3242. It still holds.
[ Note: This implies that parameter types, return type, and cv-qualifiers must match the hypothetical implicit declaration. —end note ]
Since the hypothetical implicit declaration is non-volatile you cannot have the defaulted.
Here's how you get a copy constructor and copy assignment that allows a volatile source:
struct X {
X(const X& o) : members(o.members) {}
X(const volatile X& o) : members(o.members) {}
X& operator=(const X& o) {v=o.v; return *this;}
X& operator=(const volatile X& o) {v=o.v; return *this;}
};
Note though that this has some consequences. The type is no longer POD or even trivially copyable, for one. Which might defeat the whole point of making it volatile.
You can implement the assignment operator =:
T& operator=(const volatile T &rhs) {
m_x = rhs.m_x;
return *this;
}