Why does gcc required copy constructor for implicit conversion constructor call?
class X
{
public:
X(int q) {}
~X()
{
std::cout << "~X()" << std::endl;
}
X(const X&) = delete;
};
X x = 1; // gives error: use of deleted function ‘X::X(const X&)’
What is more interesting here that if I even write copy constructor it is not called. Destructor is called only once, so the following code
class X
{
public:
X(int q) {}
~X()
{
std::cout << "~X()" << std::endl;
}
X(const X&)
{
std::cout << "copy ctor" << std::endl;
}
};
int main()
{
X x = 1;
}
prints ~X()
Is it bug? Is there any workaround?
gcc version on my locaL PC is 4.6.3, this also works the same way on another gcc version (online)
http://ideone.com/ustDRj
X x = 1; is the syntax for copy initialisation. In case the initialiser is not of type X (as here), this is semantically equivalent to this:
X x(X(1));
That is, it constructs an insance of X from argument 1, and then copy-initialises x from that instance.
Just like any other copy initialisation, the copy can be elided. This probably happens in your case, so the copy constructor is not actually called. Still, (again, just like with any other copy elision), it must be available.
you are trying to initialize a value to the class object here which calls the copy-constructor.
int main()
{
X x = 1;
}
change the code to
X x(1);
Related
This question already has an answer here:
Is implicitly deleted default constructor same as Compiler not synthesizing the default constructor
(1 answer)
Closed 9 months ago.
Given the following toy code:
class X
{
public:
X() { }
X(const X&) { }
//X(X&&) = delete;
};
int main()
{
X x;
X y = std::move(x);
}
I know that X::X(X&&) is implicitly deleted in this case because X(const X&) exists as an user-declared constructor. But I'm a little bit confused with the meaning of the terminology "implicitly deleted" here: if we uncomment X(X&&) = delete;, the code will behave differently, which means there is a difference between implicitly deleted and "explicitly" deleted.
There are two different understandings in my mind:
"implicitly deleted" means that the compiler generates some code similar to X(X&&) = delete; (that is, the compiler knows that there is X(X&&) and it knows that X(X&&) is deleted), but the code generated differs from X(X&&) = delete; in a way such that when X y = std::move(x); tries to call X::X(X&&), the compiler select X(const X&) rather than reporting an error. (Had X(X&&) = delete; been uncommented, the compiler will not select X(const X&) and will report an error)
"implicitly deleted" means that X(X&&) is not declared in the class at all, in other words, the compiler does not have any information about X(X&&) and thus X y = std::move(x); is matched directly to X(const X&).
May I ask which of my understandings is the correct one?
My guess is that the former should be the correct one. Because if we change the above code as follows:
class X
{
public:
X() { }
//X(const X&) { }
X(X&&) {}
};
int main()
{
X x;
X y = x;
}
We get an error saying 'X::X(const X &)': attempting to reference a deleted function which means the compiler knows the existence of X(const X &) when it is implicitly deleted.
However, for me, my latter understanding seems to be a more straightforward way of getting the work done. So I wonder why we want to design the concept of "implicitly deleted" (it also gives me a little feeling of inconsistency since "implicitly deleted" needs to act differently from "explicitly deleted")
When you uncomment X(X&&) = delete;, what you are doing is declaring that ctor as deleted. It means, for instance, that it does participate in overload resolution, which it wins in the case of X y = std::move(x);, but can't actually be used, because it lacks a body.
Note that this is something that doesn't apply specifically to (special or not) member functions, but to functions in general:
#include<iostream>
// this is akin to defining both copy and move ctor
auto f(int) { std::cout << "int" << std::endl;}
auto f(double) { std::cout << "double" << std::endl;}
// this is akin to defining copy ctor and deleting move ctor
auto g(int) { std::cout << "int" << std::endl;}
auto g(double) = delete;
// this is akin to defining only copy ctor
auto h(int) { std::cout << "int" << std::endl;}
// auto h(double) is not even delcared
int main() {
f(1);
f(1.2);
g(1);
//g(1.2); // compile time error
h(1);
h(1.2); // round error
}
What is specific to special member functions, is how declaring/defining/defaulting/deleteing/not-writing-at-all one influences the other.
Everything is explained at this page, but it requires a quite fine reading.
Here's a tricky bit (my bold):
Deleted implicitly-declared move constructor
The implicitly-declared or defaulted move constructor for class T is defined as deleted if any of the following is true:
T has non-static data members that cannot be moved (have deleted, inaccessible, or ambiguous move constructors);
[…]
[…]
What does the above mean?
Here's an example:
// copyable but not movable
struct CNM {
CNM() {};
CNM(CNM const&) = default;
CNM(CNM&&) = delete;
};
struct W {
W() {}
W(W&&) = default; // defaulted... but actually deleted!
CNM nm;
};
W w1;
W w2{std::move(w1)}; // compile time error
From what I learned, I thought Foo a = 1 is equivalent to Foo a = (Foo)1.
With copy constructor declared, yes, they both result in calling Converting constructor.
class Foo
{
public:
// Converting constructor
Foo(int n) : value_(n) { std::cout << "Converting constructor." << std::endl; }
// Copy constructor
Foo(const Foo&) { std::couut << "Copy constructor." << std::endl; }
private:
int value_;
};
int main()
{
Foo a = 1; // OK - prints only "Converting constructor."
Foo b = (Foo)1; // OK - prints only "Converting constructor."
}
In contrast, without copy constructor, it doesn't compile even though it doesn't ever call copy constructor.
class Foo
{
public:
// Converting constructor
Foo(int n) : value_(n) { std::cout << "Converting constructor." << std::endl; }
// Copy constructor deleted
Foo(const Foo&) = delete;
private:
int value_;
};
int main()
{
Foo a = 1; // OK - prints only "Converting constructor."
Foo b = (Foo)1; // Error C2280: 'Foo::Foo(const Foo &)': attempting to reference a deleted function
}
What makes difference?
Thank you.
Foo a = 1; calls Foo(int n). (Foo)1 also calls Foo(int n). Foo b = (Foo)1; calls copy constructor Foo(const Foo&). Until C++17 the last call can be elided but the copy constructor must be available. Since C++17 copy elision is mandatory and the copy constructor isn't necessary: https://en.cppreference.com/w/cpp/language/copy_elision
Probably you're using a C++ standard before C++17 and the copy constructor is required but not called.
"From what I learned, I thought Foo a = 1 is equivalent to Foo a = (Foo)1." That's not equivalent. The first is one constructor call and the second is two constructor calls but one call can/must be elided.
I have a C++ grammar question related to X x = X();, which results in the move constructor being invoked using special compilation flags below.
Is the grammar not in a form which one would expect the move assignment operator to be called? There is an rvalue on the right hand side of the equals sign. Curious if there is some reason behind this or history.
Also curious why commenting out the move constructor results in the copy constructor is called?
Understood that without the special flags this results in copy elision. Thanks
#include <iostream>
using namespace std;
struct X {
int x;
X() : x{0} { cout << "def cons" << endl; }
X(const X& xx) : x{xx.x} { cout << "copy cons" << endl; }
X(X&& xx) : x{xx.x} { xx.x = 0; cout << "move cons" << endl; }
X& operator=(const X& xx) { cout << "assign op" << endl; return *this; }
X& operator=(X&& xx) { xx.x = 0; cout << "move assign" << endl; return *this; }
};
int main(int argc, char *argv[])
{
X x = X();
return 0;
}
Compilation and results:
g++ -pedantic -fno-elide-constructors -Wall test145.cc && ./a.out
def cons
move cons
The object does not exist yet, it is being constructed, therefore requires a constructor. In this case the move constructor is called because of an rvalue on the RHS. A temporary object is constructed using the default constructor and this temporary object is then used to construct the object on the LHS using the move constructor.
If the class has no move constructor, then the copy constructor is called for backwards compatibility. const-lvalue-reference of the copy constructor can bind to both lvalues and rvalues, so the copy constructor is chosen in lack of a move constructor.
The move assignment operator is not called because a move assignment takes form <expr> = <expr>. In the case of this post X x = X() is used, which is not an assignment, but is a declaration that initializes x without using assignment. In this case, it’s a move construction.
I played around with explicit constructors and their behavior, so I created this class:
#include <iostream>
class X
{
public:
explicit X(void)
{
std::cout << "Default constructor\n";
}
explicit X(X const& x)
{
std::cout << "Copy constructor\n";
}
explicit X(X&& x)
{
std::cout << "Move constructor\n";
}
};
Which is basically just a stub to to test explicit constructors.
Then I wanted to try several situations. So I tried this:
X foo(void)
{
X a{};
return a; // ERROR: no matching constructor found!
}
int main()
{
X w{}; // Default Constructor
X x{w}; // Copy Constructor
X y{std::move(x)}; // Move Constructor
X z{foo()};
}
And as you can see I can't return a inside foo(). I know it tries to initialize the return type Foo with the copy constructor, but for some reason it can't use it.
How come it can't use my provided copy constructor? I know that the explicit specification causes the problem, because when I remove it from the copy constructor it works. But why?
What confuses me even more is that I can do the following:
void bar(const X& a) { /* */ }
bar(X{});
It doesn't complain. But shouldn't bar() construct it's parameter a the same way foo() constructs its return type?
When you are returning from foo:
X foo()
{
X a{};
return a;
}
You are implicitly copying a into the return of foo. But the copy constructor is marked explicit, so that is disallowed.
I'm not sure there is ever a reason to mark the copy/move constructors explicit.
I think you've misunderstood the meaning of explicit. An explicit constructor WILL NOT BE USED FOR IMPLICIT TYPE CONVERSIONS/CASTS. This means the
X foo(void){
X a{};
return a; // ERROR: no matching constructor found!
}
won't compile since you've already told the compiler not to use the copy constructor implicitly.
I reckon that what you want to achieve is "move" rather than copy a. As long as there is a (normal) move constructor in your class, a will be moved rather than copied anyway - this is the default behaviour. Actually, even with c++99, most compilers are clever enough to optimise out this copy anyway.
You can't because you said you didn't want the compiler to use it implicitly when you declared it explicit.
explicit X(X const& x)
But x has to be copied into the return value. Just change it to
X(X const& x)
and everything will work.
Live on Coliru
The Standard provides an example regarding to the a move constructor. There is what it says:
A non-template constructor for class X is a move 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).
I was trying to run some an experiments with an example the Stadard provides:
#include <iostream>
#include <limits>
struct Y {
Y(){ std::cout << "Y()" << std::endl; };
Y(const Y&){ std::cout << "Y(const Y&)" << std::endl; };
Y(Y&&){ std::cout << "Y(const Y&&)" << std::endl; };
};
Y f(int)
{
return Y();
}
Y d(f(1)); // calls Y(Y&&)
Y e = d; // calls Y(const Y&)
int main(){ }
DEMO
But instead copy constructor was called. Why that?
The copy-constructor is invoked by the line:
Y e = d;
This cannot be a move operation , because d is an lvalue. This is why you see the copy constructor call in your output.
For the line Y d(f(1)), d is moved from the rvalue f(1) (which was in turn moved from Y()), however copy elision means that the output of both of these move-constructors may be suppressed.