Template "copy constructor" does not prevent compiler-generated move constructor - c++

Consider the following program and the comments in it:
template<class T>
struct S_ {
S_() = default;
// The template version does not forbid the compiler
// to generate the move constructor implicitly
template<class U> S_(const S_<U>&) = delete;
// If I make the "real" copy constructor
// user-defined (by deleting it), then the move
// constructor is NOT implicitly generated
// S_(const S_&) = delete;
};
using S = S_<int>;
int main() {
S s;
S x{static_cast<S&&>(s)};
}
The question is: why does not user-defining the template constructor (which effectively acts as a copy constructor when U = T) prevent the compiler from generating the move constructor, while, on the contrary, if I user define the "real" copy constructor (by deleting it), then the move constructor is not generated implicitly (the program would not compile)? (Probably the reason is that "template version" does not respect the standard definition of copy-constructor also when T = U?).
The good thing is that that seems to apparently be what I want. In facts, I need all the copy and move constructors and all the move and copy assignment operators that the compiler would generate implicitly as if S was simply defined as template<class U> S{}; plus the template constructor for the conversions from other S<U>. By standard, can I rely on the above definition of S to have all the mentioned stuff I need? If yes, I could then avoid to "default'ing" them explicitly.

The answer is simple - There is no (!) template copy constructor. Even if the template parameter matches the parameter of a copy constructor it is no copy constructor.
See 12.8 Copying and moving class objects
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). [ Example: X::X(const X&)
and X::X(X&,int=1) are copy constructors.
Similar applies to the move constructor

There is no such thing as a templated copy constructor. The regular (non-templated) copy constructor is still generated and a better match than the templated one with the signature you provided (things get more complicated with "universal" references thrown in the mix, but that is off-topic).

Copy constructors, and move constructors, are not templated. The C++ standard is specific on what constitutes a copy constructor.
From: http://en.cppreference.com/w/cpp/language/copy_constructor
A copy constructor of class T is a non-template constructor whose first parameter is T&, const T&, volatile T&, or const volatile T&, and either there are no other parameters, or the rest of the parameters all have default values.

Related

Why is copy assigment possible, if a class has only a (templated) move assignment operator?

I have stumbled over code today, that I don't understand. Please consider the following example:
#include <iostream>
#include <string>
class A
{
public:
template <class Type>
Type& operator=(Type&& theOther)
{
text = std::forward<Type>(theOther).text;
return *this;
}
private:
std::string text;
};
class B
{
public:
B& operator=(B&& theOther)
{
text = std::forward<B>(theOther).text;
return *this;
}
private:
std::string text;
};
int main()
{
A a1;
A a2;
a2 = a1;
B b1;
B b2;
b2 = b1;
return 0;
}
When compiling, MinGW-w64/g++ 10.2 states:
..\src\Main.cpp: In function 'int main()':
..\src\Main.cpp:41:7: error: use of deleted function 'B& B::operator=(const B&)'
41 | b2 = b1;
| ^~
..\src\Main.cpp:19:7: note: 'B& B::operator=(const B&)' is implicitly declared as deleted because 'B' declares a move constructor or move assignment operator
19 | class B
| ^
mingw32-make: *** [Makefile:419: Main.o] Error 1
I fully understand the error message. But I don't understand why I don't get the same message with class A. Isn't the templated move assignment operator also a move assignment operator? Why then is the copy assignment operator not deleted? Is this well-written code?
Isn't the templated move assignment operator also a move assignment operator?
No, it's not considered as move assignment operator.
(emphasis mine)
A move assignment operator of class T is a non-template non-static member function with the name operator= that takes exactly one parameter of type T&&, const T&&, volatile T&&, or const volatile T&&.
As the effect, A still has the implicitly-declared copy/move assignment operator.
BTW: Your template assignment operator takes forwarding reference, it could accept both lvalue and rvalue. In a2 = a1;, it wins against the generated copy assignment operator in overload resolution and gets called.
As complement to #songyuanyao's standard-based answer: these kind of language rules are common reasons for guidelines such as MISRA/AUTOSAR (language guidelines for safety-critical C++ development) to have "avoid developer confusion" rules such as:
(from AUTOSAR C++14 Guidelines)
Rule A14-5-1 (required, implementation, automated)
A template constructor shall not participate in overload resolution
for a single argument of the enclosing class type.
Rationale
A template constructor is never a copy or move constructor and
therefore doesn’t prevent the implicit definition of a copy or move
constructor even if the template constructor looks similar and might
easily be confused. At the same time, copy or move operations do not
necessarily only use a copy or move constructor, but go through the
normal overload resolution process to find the best matching function
to use. This can cause confusion in the following cases:
a template constructor that looks like a copy/move constructor is not selected
for a copy/move operation because the compiler has generated an implicit copy/move constructor as well a template constructor is
selected in preference over a copy/move constructor because the
template constructor is a better match
To avoid these confusing situations, template constructors shall not
participate in overload resolution for a single argument of the
enclosing class type to avoid a template constructor being selected
for a copy/move operation. It also makes it clear that the constructor
is not a copy/move constructor and that it does not prevent the
implicit generation of copy/move constructors.
Rule M14-5-3 (required, implementation, automated)
A copy assignment operator shall be declared when there is a template
assignment operator with a parameter that is a generic parameter.
Namely that it can be surprising for developers that a template copy/move ctor/assignment operator does not suppress (/does not "activate" rule of 5) implicitly-generated ones. You are typically required to use SFINAE to make sure that the templated ctor/assignment op does not act as if it was a copy/move ctor/assignment by allowing the overload to be active for a single argument of the enclosing class.

Move constructor template elision

For what reason move constructor call is not elided in following code?
struct Foo {
Foo() = default;
Foo(const Foo&) { cout << "Foo(const Foo&)" << endl; }
template<class T> Foo(T&&) { cout << "Foo<T>(T&&)" << endl; }
};
auto f = Foo{};
Output: Foo<T>(T&&)
Checked on clang 3.3, g++ 4.9.
Adding defaulted or user-defined move constructor results in no output at all. Why the call to move constructor template is not elided by the compiler? And why the call to non-template one gets elided even if it's user-defined (i.e. how does the compiler know that it has no side-effects and can be safely elided)?
To be a move constructor and thus candidate for elision, the constructor must not be a template instantiation (similarly, the fact that a template can generate a constructor with the same signature will not prevent the defaulted version to be generated).
In your example, the presence of the explicit copy constructor prevent the implicit generation of a default move constructor, so the constructor template is instantiated (and is not a move constructor even if it share the signature).
If you add an explicit (default or not) move constructor, it will be used (and can -- but not must -- be elided).
The reason is because copy and move constructors are defined to be non-template (12.8/2, 12.8/3), copy/move elision rules do not apply for this constructor template (12.8/31).
The addition of defaulted or user defined move constructor (in this case) results in different output (and move elision) because user-defined copy constructor prevents compiler from implicitly generating a default one, thus the answer was wrong in first place:
12.8/7
If the class definition does not explicitly declare a copy constructor, 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 (8.4).

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.

Conversion by constructors

Class X -> converted to Y by two ways 1) constructors, and 2) by conversion functions.
I understood the single argument constructor is used for conversion.
In the specification:
An implicitly-declared copy constructor is not an explicit constructor; it may be called for implicit type conversion.
Question:
So, that means not only single argument constructor is used for the conversion, but also copy constructor?. If so, which scenario it is used?. any snippet of sample code?
Kindly bear with me if the question is very basis.
Copy constructor is not an explicit constructor, so it will be used wherever possible. Copy constructor will "convert" only from the same type, so it is not a conversion in the full sense. However, for the sake of generality it is handy to call it one.
Read this paper: http://www.keithschwarz.com/cs106l/winter20072008/handouts/180_Conversion_Constructors.pdf if you want more details on conversion constructors.
It basically means that you can do:
struct A {};
A a;
A b = a;
If the copy constructor was marked explicit that would fail to compile. You can test it by adding: explicit A( A const & ) {} to the struct and recompiling the program.
Implicitly-declared copy constructor cannot use for conversions, since it's copy-ctor, that has declared as T(const T&) or T(T&).
draft n3337 par 12.8 C++ standard.
8 The implicitly-declared copy constructor for a class X will have the
form X::X(const X&) if — each direct or virtual base class B of X has
a copy constructor whose first parameter is of type const B& or const
volatile 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
constructor whose first parameter is of type const M& or const
volatile M&.119 Otherwise, the implicitly-declared copy constructor
will have the form X::X(X&)
Since copy c-tor is not explicit you can use such code
struct So
{
};
int main()
{
So s = So();
}
If copy-ctor is explicit you could use only initizaliation like So s((So()));
An implicit copy constructor is one that the compiler writes for you. It always has the form
T(const T&);
This means that any object which has a conversion operator to const T& can be implicitly copied, even if this isn't what you wanted. The most common way to trigger this is to make a copy to a base class from a derived class; this is called object slicing because the copy won't have the same type as the original and will probably lose some important properties or behavior.

Does a templated constructor override the implicit copy constructor in C++?

Does a templated constructor (such as the following) override the implicit copy constructor?
template <class T>
struct Foo
{
T data;
// ...
template <class U>
Foo(const Foo<U> &other) : data((T)doSomethingWith(other.data)) {}
// ...
};
If so, does it still override it if other is passed by value rather than constant reference?
If so, is there any way around this without explicitly defining a copy constructor?
No, that is not a copy constructor. Section 12.8 ([class.copy]) of the Standard requires that:
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.
The compiler will still implicitly generate a defaulted one.
You can make that explicit (requires C++11) by
Foo(const Foo<T>&) = default;
Does a templated constructor (such as the following) override the implicit copy constructor?
No. The copy constructor is still implicitly declared, and is chosen in preference to the template.
Is there any way around this without explicitly defining a copy constructor?
No. If you don't want the implicit copy constructor, then you'll have to define one yourself.
A templated constructor or assignment operator which looks like a templated [default constructor/copy constructor/move constructor/copy assignment operator/move assignment operator] is not really a [default constructor/copy constructor/move constructor/copy assignment operator/move assignment operator] and will not replace it or prevent it from being implicitly generated.