I have the following code:
template <typename T>
struct X {
X() : x(5) {}
template <template <typename> class S>
X(const S<T> & y) : x(y.x) {}
// Uncomment and it does not compile anymore
// X(X&& z) : x(z.x) {}
T x;
};
int main ()
{
X<int> y;
auto x = y;
return 0;
}
Could you explain why it does not compile once the specified code is uncommented?
Copy constructor is implicitly declared as deleted because you declared a move constructor.
Add a default (or user defined) copy constructor and it would compile fine:
X(const X<T>&)=default;
Humam Helfawi got the point.
I try to complete his answer.
Svalorzen: look at this code
#include <iostream>
template <typename T>
struct X {
X() : x(5) {}
template <template <typename> class S>
X (const S<T> & y) : x(y.x)
{ std::cout << "templated constructor" << std::endl; }
X (const X<T> & y) : x(y.x)
{ std::cout << "copy constructor" << std::endl; }
T x;
};
int main ()
{
X<int> y;
auto x = y;
return 0;
}
The output is "copy constructor".
When the compiler find a matching templated function and a matching plain (no templated) function, choose the plain as more specific.
Now delete the definition of the copy contructor
#include <iostream>
template <typename T>
struct X {
X() : x(5) {}
template <template <typename> class S>
X (const S<T> & y) : x(y.x)
{ std::cout << "templated constructor" << std::endl; }
T x;
};
int main ()
{
X<int> y;
auto x = y;
return 0;
}
and you get your example without the move constructor and with the cout ("templated constructor") added.
You can see that the output is empty.
That's because the compiler choose the copy contructor that, as default version, is implicitly present.
When you add the move constructor, you incidentally mark as deleted the copy constructor. But the copy constructor is ever present (even as marked as deleted) and the compiler consider it. So, when the compiler try to implement "x = y", match your your templated constructor, match the copy constructor, choose the copy constructor (more specific), see that is deleted and give error.
When you add
X (const X<T> &) = default;
you permit the compiler to use the copy constructor.
p.s.: sorry for my bad English.
Related
I have the following code and its output printed below. I can't seem to understand why one set of braced initialization results in the move constructor being called, while the other results in the copy constructor. I have somewhat narrowed it down to direct-list-initialization vs copy-list-initialization per https://en.cppreference.com/w/cpp/language/list_initialization
I just can't quite figure out which case my code belongs to. Thanks in advance.
#include <cstdint>
#include <iostream>
using namespace std;
struct Foo {
Foo() {
cout << "create foo\n";
}
~Foo() {
cout << "delete foo\n";
}
Foo(const Foo& f) {
cout << "copy foo\n";
}
Foo(Foo&& f) noexcept {
cout << "move foo\n";
}
Foo& operator=(const Foo& f) = delete;
Foo& operator=(Foo&& f) = delete;
};
int32_t main() {
pair<uint32_t, Foo> f1{0, Foo{}}; // Calls move ctor
cout << "------------------------\n";
pair<uint32_t, Foo> f2{0, {}}; // Calls copy ctor
cout << "------------------------\n";
return 0;
}
This results in
create foo
move foo
delete foo
------------------------
create foo
copy foo
delete foo
------------------------
delete foo
delete foo
Let's take a look at two of the two-argument constructors of pair: [pairs.pair]
EXPLICIT constexpr pair(const T1& x, const T2& y);
template<class U1, class U2> EXPLICIT constexpr pair(U1&& x, U2&& y);
The second constructor uses perfect forwarding, and U2 cannot be deduced from {}. Therefore, the first version is selected when {} is used. When Foo{} is used instead, the argument has type Foo, so U2 is deduced to be Foo, causing the forwarding version to be selected.
Following the code-snippet below, I attempt to pass a Boo<int> instance through the Boo<T>::Boo(Foo const &) constructor overload, which I cannot manage to do.
#include <iostream>
struct Foo { };
template <typename T>
struct Boo : public Foo
{
// Boo(Boo<T> const &) = delete; // Leaves (2) without a constructor
Boo() { std::cout << "Beep " << sizeof(T) << std::endl; }
Boo(Foo const &) { std::cout << "Boop " << sizeof(T) << std::endl; }
};
void fun(Foo const &) { }
int main()
{
Boo<int> x; // (1) Output: Beep 4
Boo<int> y(x); // (2) Output:
Boo<double> z(x); // (3) Output: Boop 8
fun(x); // (4) Compiles
return 0;
}
In the code-snippet I tried to write a simplistic scenario which can be copy-pasted to play around with, if need be.
At (1), we generate a Boo<int> instance x, which uses the Boo<T>::Boo() constructor overload.
At (2), we pass instance x to the constructor of instance y, which uses the implicitly defined copy constructor Boo<T>::Boo(Boo<T> const &). Hence, we do not receive an output message.
At (3), we pass instance x to the constructor of instance z, which uses the Boo<T>::Boo(Foo const &) constructor overload.
At (4), we confirm that Boo<int> can be implicitly converted to Foo const & by the compiler and passed into the fun(Foo const &) function.
Question: How can I get (2) to go through the same constructor as (3) does, and why does it not already do that?
If anyone can see what I have missed, I would much appreciate it, were it pointed out to me.
Use a delegating constructor:
template <typename T>
struct Boo : public Foo
{
Boo(Boo<T> const & arg) : Boo(static_cast<Foo const&>(arg)) {};
Boo() { std::cout << "Beep " << sizeof(T) << std::endl; }
Boo(Foo const &) { std::cout << "Boop " << sizeof(T) << std::endl; }
};
The reason this fixes it is that there is no longer an implicit copy constructor, which would have been a better match than casting to Foo const& and using constructor Boo<T>::Boo(Foo const&), and calls it manually.
When I run the code below it produces the following output,
The first part used the template directly,
the second using a class that is derived from the template.
The moving semantics is not invoked in the derived class (shown in bold)
Template Dummy: initializing constructor
Template Dummy: initializing constructor
Template Dummy: empty constructor
Template Dummy: empty constructor
Template Dummy: + operator
Template Dummy: move assignment
2
Template Dummy: initializing constructor
Template Dummy: initializing constructor
Template Dummy: empty constructor
Template Dummy: empty constructor
Template Dummy: + operator
Template Dummy: copy constructor
Template Dummy: copy assignment
2
The reason, I think, is clear - naming an argument turns the argument into an lvalue, thus the template receives an lvalue and invokes a copy constructor.
The question is how to force move semantics in this case?
#include <iostream>
using namespace std;
template <typename T> class Dummy {
public:
T val;
Dummy& operator=(const Dummy& d){
val = d.val;
cout << "Template Dummy: copy assignment\n" ;
return *this;
}
Dummy operator+(const Dummy &d) {
Dummy res;
res.val = val + d.val;
cout << "Template Dummy: + operator\n" ;
return res;
}
// constructors
Dummy() {
val = 0;
cout << "Template Dummy: empty constructor\n" ;
}
Dummy(const T v) {
val = v;
cout << "Template Dummy: initializing constructor\n" ;
}
Dummy(const Dummy &d) {
val = d.val;
cout << "Template Dummy: copy constructor\n" ;
}
// move semantics
Dummy(const Dummy&& d) {
val = d.val;
cout << "Template Dummy: move constructor\n" ;
}
Dummy& operator=(const Dummy&& d){
val = d.val;
cout << "Template Dummy: move assignment\n" ;
return *this;
}
};
class FloatDummy : public Dummy<float> {
public:
FloatDummy& operator=(const FloatDummy& d){
Dummy<float>::operator=(d);
return *this;
}
FloatDummy operator+(const FloatDummy &d) {
return (FloatDummy) Dummy<float>::operator+(d);
}
// constructors
FloatDummy() : Dummy<float>() {};
FloatDummy(float v) : Dummy<float>(v) {}
FloatDummy(const FloatDummy &d) : Dummy<float>(d) {}
FloatDummy(const Dummy<float> &d) : Dummy<float>(d) {}
// move semantics
FloatDummy(const FloatDummy&& d) : Dummy<float>(d) {}
FloatDummy& operator=(const FloatDummy&& d){
// here d is already an lvalue because it was named
// thus the template invokes a copy assignment
Dummy<float>::operator=(d);
return *this;
}
};
int main() {
Dummy<float> a(1), b(1);
Dummy<float> c;
c = a + b;
cout << c.val << '\n';;
FloatDummy d(1), e(1);
FloatDummy f;
f = d + e;
cout << f.val << '\n';
}
You cannot move from a const&. To fix, simply remove const from all your move operations:
FloatDummy(FloatDummy&& d) : Dummy<float>(d) {}
// ^^^^^^^^^^^^ no longer FloatDummy const&&
FloatDummy& operator=(FloatDummy&& d) { /*...*/ }
// ^^^^^^^^^^^^ ditto
The essence of moving is taking ownership of resources, how can you take ownership of something if you can't modify your source? Therefore, move operations do not work with const types by default.
You'll want to do the same for the Dummy class.
You need to call std::move() to cast d to an rvalue reference: Even though d's parameter type is rvalue reference, inside the function itself d is pretty much considered an lvalue reference. This means that you still need to cast it to rvalue reference (the whole point of std::move()) when you pass it to whatever is actually consuming it.
In your case, it's right when you pass it to Dummy<float>'s constructor or Dummy<float>::operator=:
FloatDummy(FloatDummy&& d) : Dummy<float>(std::move(d)) {}
// ^^^^^^^^^ calling std::move()
// otherwise copy ctor overload is chosen
FloatDummy& operator=(FloatDummy&& d)
{
Dummy<float>::operator=(std::move(d));
// ^^^^^^^^^ ditto
return *this;
}
I have this simple example holder class which is explicitly non-moveable:
template <typename T>
struct holder
{
holder() = default;
holder(const holder& b)
: t(b.t)
{
}
holder(holder&& b) = delete;
holder& operator=(const holder& b)
{
t = b.t;
return *this;
}
holder& operator=(holder&& b) = delete;
T t;
};
The following type is therefore also implicitly non-copyable (because of std::unique_ptr being such):
typedef holder<std::unique_ptr<int>> ptr;
So, as I would expect if I have a function like ptr foo();, calling it by either auto x = foo; or ptr x; x = foo(); produces a compilation error that a deleted function is being called.
However if I introduce another type a vector of ptr like so:
typedef std::vector<ptr> vec;
vec foo();
int main()
{
vec x = foo();
x = foo();
return 0;
}
...this compiles fine.
How come? How does this even work?
(an example of the successful compilation can be found here)
Let alone RVO, a vector is movable independently from the element type's characteristics.
Let's consider the following examples:
I.
#include <iostream>
struct X {
X(){ };
X(const X&, int i = 6);
};
X::X(const X& x, int i) { std::cout << "ctor" << std::endl; }
X x;
X z = x;
int main()
{
}
DEMO
II.
#include <iostream>
struct X {
X(){ };
X(const X&, int i);
};
X::X(const X& x, int i = 7) { std::cout << "ctor" << std::endl; } //error:
//addition of default argument on redeclaration makes this constructor a copy constructor
X x;
X z = x;
int main()
{
}
DEMO
III
#include <iostream>
void foo(int i);
void bar(int j = 7);
void foo(int i = 7){ }
void bar(int j){ }
int main()
{
}
DEMO
Examples I and II are well-formed. But why can't we overload a constructor in the same way as a function (Example II)? How does the Standard prevent it?
The point is that whether a class is trivial, or trivially copyable, etc., should really be decidable based on the class definition and not require knowledge of the whole program. ยง8.3.6 [dcl.fct.default]/p6 (quoting N4140):
Except for member functions of class templates, the default arguments
in a member function definition that appears outside of the class
definition are added to the set of default arguments provided by the
member function declaration in the class definition; the program is
ill-formed if a default constructor (12.1), copy or move constructor,
or copy or move assignment operator (12.8) is so declared.
See CWG issue 1344.
You can overload constructors in the same way as functions. The problem with sample II is that the compiler can't see the difference between
X::X(const X& x) //The copy constructor
and
X::X(const X& x, int i = 7) //A normal constructor
in your case : X z = x;
That's because the compiler expands it to X z(x);, which could be the normal or the copy constructor.