Ramification of assignment operators with values instead of references - c++

This question comes from issues raised by this answer.
Normally, we define copy assignment operators for type T as T& operator=(const T&), and move assignment operators for type T as T& operator=(T&&).
However, what happens when we use a value parameter rather than a reference?
class T
{
public:
T& operator=(T t);
};
This should make T both copy and move assignable. However, what I want to know is what are the language ramifications for T?
Specifically:
Does this count as a copy assignment operator for T, according to the specification?
Does this count as a move assignment operator for T, according to the specification?
Will T have a compiler-generated copy assignment operator?
Will T have a compiler-generated move assignment operator?
How does this affect traits classes like std::is_move_assignable?

Most of this is described in §12.8. Paragraph 17 defines what counts as user-declared copy assignment operators:
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&.
Paragraph 19 defines what counts as user-declared move assignment operators:
A user-declared move assignment operator X::operator= is a non-static
non-template member function of class X with exactly one parameter of
type X&&, const X&&, volatile X&&, or const volatile X&&.
So, it counts as a copy assignment operator, but not as a move assignment operator.
Paragraph 18 tells when the compiler generates copy assignment operators:
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.
Paragraph 20 tells us when the compiler generates move assignment operators:
If the definition of a class X does not explicitly declare a move
assignment operator, one will be implicitly declared as defaulted if
and only if
[...]
— X does not have a user-declared copy assignment operator,
[...]
Since the class has a user-declared copy assignment operator, neither of the implicit ones will be generated by the compiler.
std::is_copy_assignable and std::is_move_assignable are described in table 49 as having the same value as, respectively is_assignable<T&,T const&>::value and is_assignable<T&,T&&>::value. That table tells us that is_assignable<T,U>::value is true when:
The expression declval<T>() = declval<U>() is well-formed when treated
as an unevaluated operand (Clause 5). Access checking is performed as
if in a context unrelated to T and U. Only the validity of the
immediate context of the assignment expression is considered.
Since both declval<T&>() = declval<T const&>() and declval<T&>() = declval<T&&>() are well-formed for that class, it still counts as copy assignable and move assignable.
As I mentioned in the comments, what's curious about all this is that, in the presence of a move constructor, that operator= will correctly perform moves, but technically not count as a move assignment operator. It's even stranger if the class has no copy constructor: it will have a copy assignment operator that doesn't do copies, but only moves.

Related

C++ rule of 5 and rule of 0 [duplicate]

I want to refresh my memory on the conditions under which a compiler typically auto generates a default constructor, copy constructor and assignment operator.
I recollect there were some rules, but I don't remember, and also can't find a reputable resource online. Can anyone help?
In the following, "auto-generated" means "implicitly declared as defaulted, but not defined as deleted". There are situations where the special member functions are declared, but defined as deleted.
The default constructor is auto-generated if there is no user-declared constructor (§12.1/5).
The copy constructor is auto-generated if there is no user-declared move constructor or move assignment operator (because there are no move constructors or move assignment operators in C++03, this simplifies to "always" in C++03) (§12.8/8).
The copy assignment operator is auto-generated if there is no user-declared move constructor or move assignment operator (§12.8/19).
The destructor is auto-generated if there is no user-declared destructor (§12.4/4).
C++11 and later only:
The move constructor is auto-generated if there is no user-declared copy constructor, copy assignment operator or destructor, and if the generated move constructor is valid (§12.8/10).
The move assignment operator is auto-generated if there is no user-declared copy constructor, copy assignment operator or destructor, and if the generated move assignment operator is valid (e.g. if it wouldn't need to assign constant members) (§12.8/21).
I've found the diagram below very useful.
from Sticky Bits - Becoming a Rule of Zero Hero
C++17 N4659 standard draft
For a quick cross standard reference, have a look at the "Implicitly-declared" sections of the following cppreference entries:
https://en.cppreference.com/w/cpp/language/copy_constructor
https://en.cppreference.com/w/cpp/language/move_constructor
https://en.cppreference.com/w/cpp/language/copy_assignment
https://en.cppreference.com/w/cpp/language/move_assignment
The same information can of course be obtained from the standard. E.g. on C++17 N4659 standard draft:
15.8.1 "Copy/move constructors" says for for copy constructor:
6 If the class definition does not explicitly declare a copy constructor, a non-explicit one is declared implicitly.
If the class definition declares a move constructor or move assignment operator, the implicitly declared copy
constructor is defined as deleted; otherwise, it is defined as defaulted (11.4). The latter case is deprecated if
the class has a user-declared copy assignment operator or a user-declared destructor.
and for move constructor:
8 If the definition of a class X does not explicitly declare a move constructor, a non-explicit one will be implicitly
declared as defaulted if and only if
(8.1)
— X does not have a user-declared copy constructor,
(8.2)
— X does not have a user-declared copy assignment operator,
(8.3)
— X does not have a user-declared move assignment operator, and
(8.4)
— X does not have a user-declared destructor.
15.8.2 "Copy/move assignment operator" says for copy assignment:
2 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 (11.4). The latter
case is deprecated if the class has a user-declared copy constructor or a user-declared destructor.
and for move assignment:
4 If the definition of a class X does not explicitly declare a move assignment operator, one will be implicitly
declared as defaulted if and only if
(4.1) — X does not have a user-declared copy constructor,
(4.2) — X does not have a user-declared move constructor,
(4.3) — X does not have a user-declared copy assignment operator, and
(4.4) — X does not have a user-declared destructor.
15.4 "Destructors" says it for destructors:
4 If a class has no user-declared destructor, a destructor is implicitly declared as defaulted (11.4). An
implicitly-declared destructor is an inline public member of its class.

Why doesn't perfect forwarding (catch-all) work to implement a copy-assignment?

In a class with a series of ctors (most of which have exactly one argument), I want all one-argument ctors to also be mirrored by a corresponding assignment operator. The ctors include, but are not limited to a copy-ctor and a move-ctor. So this, should satisfy the rule-of-five.
template <typename T>
object& operator=(T&& from) {
// ...
return *this;
}
Here is a minimal example: https://ideone.com/OKprcr (thanks to #Daniel H for pointing out constness).
The error I get is
error: object of type 'object' cannot be assigned because its copy assignment operator is implicitly deleted
...
note: copy assignment operator is implicitly deleted because 'object' has a user-declared move constructor
Why doesn't the function template implement the copy-assignment operator?
Why doesn't the function template implement the copy-assignment operator?
Because the standard says so ([class.copy.assign]/1):
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&.
Note there's no X&& in there either.

C++ object as parameter, but no constructor for it [duplicate]

I want to refresh my memory on the conditions under which a compiler typically auto generates a default constructor, copy constructor and assignment operator.
I recollect there were some rules, but I don't remember, and also can't find a reputable resource online. Can anyone help?
In the following, "auto-generated" means "implicitly declared as defaulted, but not defined as deleted". There are situations where the special member functions are declared, but defined as deleted.
The default constructor is auto-generated if there is no user-declared constructor (§12.1/5).
The copy constructor is auto-generated if there is no user-declared move constructor or move assignment operator (because there are no move constructors or move assignment operators in C++03, this simplifies to "always" in C++03) (§12.8/8).
The copy assignment operator is auto-generated if there is no user-declared move constructor or move assignment operator (§12.8/19).
The destructor is auto-generated if there is no user-declared destructor (§12.4/4).
C++11 and later only:
The move constructor is auto-generated if there is no user-declared copy constructor, copy assignment operator or destructor, and if the generated move constructor is valid (§12.8/10).
The move assignment operator is auto-generated if there is no user-declared copy constructor, copy assignment operator or destructor, and if the generated move assignment operator is valid (e.g. if it wouldn't need to assign constant members) (§12.8/21).
I've found the diagram below very useful.
from Sticky Bits - Becoming a Rule of Zero Hero
C++17 N4659 standard draft
For a quick cross standard reference, have a look at the "Implicitly-declared" sections of the following cppreference entries:
https://en.cppreference.com/w/cpp/language/copy_constructor
https://en.cppreference.com/w/cpp/language/move_constructor
https://en.cppreference.com/w/cpp/language/copy_assignment
https://en.cppreference.com/w/cpp/language/move_assignment
The same information can of course be obtained from the standard. E.g. on C++17 N4659 standard draft:
15.8.1 "Copy/move constructors" says for for copy constructor:
6 If the class definition does not explicitly declare a copy constructor, a non-explicit one is declared implicitly.
If the class definition declares a move constructor or move assignment operator, the implicitly declared copy
constructor is defined as deleted; otherwise, it is defined as defaulted (11.4). The latter case is deprecated if
the class has a user-declared copy assignment operator or a user-declared destructor.
and for move constructor:
8 If the definition of a class X does not explicitly declare a move constructor, a non-explicit one will be implicitly
declared as defaulted if and only if
(8.1)
— X does not have a user-declared copy constructor,
(8.2)
— X does not have a user-declared copy assignment operator,
(8.3)
— X does not have a user-declared move assignment operator, and
(8.4)
— X does not have a user-declared destructor.
15.8.2 "Copy/move assignment operator" says for copy assignment:
2 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 (11.4). The latter
case is deprecated if the class has a user-declared copy constructor or a user-declared destructor.
and for move assignment:
4 If the definition of a class X does not explicitly declare a move assignment operator, one will be implicitly
declared as defaulted if and only if
(4.1) — X does not have a user-declared copy constructor,
(4.2) — X does not have a user-declared move constructor,
(4.3) — X does not have a user-declared copy assignment operator, and
(4.4) — X does not have a user-declared destructor.
15.4 "Destructors" says it for destructors:
4 If a class has no user-declared destructor, a destructor is implicitly declared as defaulted (11.4). An
implicitly-declared destructor is an inline public member of its class.

Does deleting a copy constructor or copy assignment operator count as "user declared"?

Per this presentation, if either the copy constructor or copy assignment operator is "user declared", then no implicit move operations will be generated. Does deleteing the copy constructor or copy assignment operator count as "user declared"?
struct NoCopy {
NoCopy(NoCopy&) = delete;
NoCopy& operator=(const NoCopy&) = delete;
};
Will implicit move operations be generated for the NoCopy class? Or does deleting the relevant copy operations count as "user declared" and thus inhibit implicit move generation?
If possible, I'd prefer an answer referencing the relevant parts of the standard.
According to slide 14 of your presentation, a deleted copy constructor is "user declared" thus inhibiting the move generation.
The term "user declared" doesn't have a formal definition in the standard. It is meant to be the opposite of "implicitly declared" in the context of special member functions. [dcl.fct.def.default]/4 could be a bit clearer about this fact, but the intention is there:
Explicitly-defaulted functions and implicitly-declared functions are collectively called defaulted functions, and the implementation shall provide implicit definitions for them (12.1 12.4, 12.8), which might mean defining them as deleted. A special member function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration. A user-provided explicitly-defaulted function (i.e., explicitly defaulted after its first declaration) is defined at the point where it is explicitly defaulted; if such a function is implicitly defined as deleted, the program is ill-formed.
Both NoCopy(NoCopy&) = delete; and NoCopy& operator=(const NoCopy&) = delete; are declarations of special member functions. Since you are explicitly declaring them, as opposed to allowing the compiler to declare them implicitly, they are user-declared. Those declarations will therefore suppress the implicit declarations of the move constructor and move assignment operator per [class.copy]/9:
If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared as defaulted if and only if
— X does not have a user-declared copy constructor,
— X does not have a user-declared copy assignment operator,
— X does not have a user-declared move assignment operator,
— X does not have a user-declared destructor, and
— the move constructor would not be implicitly defined as deleted.

Do I still get default copy constructor and operator= if I define ones with non-const arguments?

In C++, if I define a copy constructor and operator= that take a non-const reference to the class, is the compiler supposed to still supply default versions for const reference?
struct Test {
Test(Test &rhs);
Test &operator=(Test &rhs);
private:
// Do I still need to declare these to avoid automatic definitions?
Test(const Test &rhs);
Test &operator=(const Test &rhs);
};
No, if you define a copy constructor and assignment operator, the compiler will not implicitly declare or define it's own. Note that the definition of copy-constructor allows for the argument to be taken by either const or non-const reference, so your constructor is indeed a copy-constructor. Similarly for operator=
[Omitting a big part of the details, in particular under what circumstances the implicitly declared special member functions will also be implicitly defined]
12.8 [class.copy]/2 A non-template constructor for class X is a copy constructor if its first parameter is of type X&, const X&, volatile X& or const volatile X&, and either there are no other parameters or else all other parameters have default arguments (8.3.6).
12.8 [class.copy]/7 If the class definition does not explicitly declare a copy constructor, one is declared implicitly.
12.8 [class.copy]/17 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&.
12.8 [class.copy]/18 If the class definition does not explicitly declare a copy assignment operator, one is declared implicitly.
No, once you declare your own copy constructor or copy assignment operator (whether or not it uses the canonical constness) the compiler won't do it for you anymore.
But doing this by non-const reference is pretty much a textbook example of violating the principle of least surprise. Everyone expects that const objects can be assigned from and that the right hand side won't be mutated. The first isn't so bad as the compiler will catch it but the second could cause a variety of hard-to-spot bugs.
If you're trying to implement move semantics and you can't use C++11, I would suggest creating a special move method and just not allowing "move" construction at all. If you can use C++11 then use the builtin rvalue references.