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.
Related
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.
So I am new to move semantic and I am testing out the following code. My understanding is that rvalue will invoke the move constructor and I expected A("123") will cause the move constructor to be called. But when I ran this, the copy constructor is called instead.
#include <string>
#include <iostream>
#include <utility>
class A
{
std::string s;
public:
A(const std::string& in) : s(in) { std::cout << "ctor!\n";}
A(const A& o) : s(o.s) { std::cout << "move failed!\n"; }
A(A&& o) noexcept : s(std::move(o.s)) { }
};
class B
{
A d_a;
public:
B(const A& a) :d_a(a)
{}
};
int main()
{
std::cout << "Trying to move A\n";
B b(A("123")); // move-constructs from rvalue temporary
}
The issue is the constructor of B:
B(const A& a) :d_a(a) {}
The function parameter const A& is a const-qualified lvalue reference, which you can't cast to an rvalue. You need to change the constructor to (or add a second one)
B(A&& a) : d_a(std::move(a)) {}
As a side note, you get the correct move semantics for the types in your example for free, if you just define them as
struct A {
std::string s;
};
struct B {
A d_a;
};
with the client code
B b{A{"123"}};
I understand that you wanted to not rely on compiler-generated special member functions for the sake of investigating the move-construction, I just didn't want to omit this shortcut, because this is the setup one should strive for: let the copy and move semantics of your class be automatically assembled by their data members.
A const lvalue reference binds to anything. Your B has a constructor that takes A by a const lvalue reference. When you pass a temporary A to this constructor, this will eventually lead to some parameter setting that looks like
const A& tmp = temporary_A;
and from then on, your A will be treated as a const lvalue reference which makes a call to copy constructor since that matches the signature. You need to define a constructor in B that takes A by an rvalue reference.
B(A&& a)
As things currently are, you will see a print out like:
Trying to move A
ctor!
move failed!
ctor! is printed when constructing the temporary and since your parameter is taken as const lvalue reference d_a(a) calls the copy constructor. Below, modified code avoids copy.
#include <string>
#include <iostream>
#include <utility>
class A
{
std::string s;
public:
A(const std::string& in) : s(in) { std::cout << "ctor!\n";}
A(const A& o) : s(o.s) { std::cout << "move failed!\n"; }
A(A&& o) noexcept : s(std::move(o.s)) { }
};
class B
{
A d_a;
public:
B(A&& a) :d_a(std::move(a))
{}
};
int main()
{
std::cout << "Trying to move A\n";
B b(A("123")); // move-constructs from rvalue temporary
}
I have a question about the following code. My compiler is MSVC++ 17 Visual studio version 15.3 with compiler option /std:c++14 (as opposed to /std:c++latest) running in release mode:
struct Bar
{
int a;
std::string b;
Bar() { std::cout << "default\n"; }
Bar(int a, const std::string& b) : a{ a }, b{ b } { std::cout << "direct\n"; }
Bar(int a, std::string&& b) : a{ a }, b{ std::move(b) } { std::cout << "direct move b\n"; }
Bar(const Bar& other) : a{ other.a }, b{ other.b } { std::cout << "const copy\n"; }
Bar(Bar&& other) : a{ std::move(other.a) }, b{ std::move(other.b) } { std::cout << "move\n"; }
Bar& operator=(const Bar& other)
{
a = other.a;
b = other.b;
std::cout << "const assign\n";
return *this;
}
Bar& operator=(Bar&& other)
{
a = std::move(other.a); //would this even be correct?
b = std::move(other.b);
std::cout << "move assign\n";
return *this;
}
};
std::tuple<Bar, Bar> foo()
{
std::string s = "dsdf";
return { { 1, s }, { 5, "asdf" } };
}
int main()
{
Bar a, b;
std::tie(a, b) = foo();
std::cout << a.a << a.b << std::endl;
std::cout << b.a << b.b;
}
The output is:
default
default
direct
direct move b
const copy <-- Why copy? Why not move>
const copy <-- Why copy? Why not move>
move assign
move assign
1dsdf
5asdf
If I change return { { 1, s }, { 5, "asdf" } }; to return { Bar{ 1, s }, Bar{ 5, "asdf" } }; the output changes to:
default
default
direct
direct move b
move
move
move assign
move assign
1dsdf
5asdf
Question: Why isn't a move performed in both cases? Why is the copy constructor called in the first case?
The simplest distillation of your question is why:
std::tuple<Bar> t{{5, "asdf"}};
prints
direct move b
const copy
but
std::tuple<Bar> u{Bar{5, "asdf"}};
prints
direct move b
move
To answer that question, we have to determine what those two declarations actually do. And in order to do that, we have to understand which of std::tuple's constructors get called. The relevant ones are (neither the explicitness and constexprness of each constructor is relevant, so I am omitting them for brevity):
tuple( const Types&... args ); // (2)
template< class... UTypes >
tuple( UTypes&&... args ); // (3)
Initializing with Bar{5, "asdf"} would invoke constructor (3) as the better match (both (2) and (3) are viable, but we get a less cv-qualified reference in (3)), which would forward from the UTypes into the tuple. This is why we end up with move.
But initializing with just {5, "asdf"}, this constructor isn't viable because braced-init-lists have no type that can be deduced. Hence our only option is (2), and we end up with a copy.
The only way to fix this would be to add non-template constructors that take rvalue references to each of the Types. But you would need 2^N-1 such constructors (all but the one that takes all const lvalue references - since that one could be deduced), so instead we end up with a design that works in all cases but is suboptimal. But since you could just specify the type you want on the call site, this isn't a huge defect.
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.
The first example:
#include <iostream>
#include <memory>
using namespace std;
struct A {
unique_ptr<int> ref;
A(const A&) = delete;
A(A&&) = default;
A(const int i) : ref(new int(i)) { }
~A() = default;
};
int main()
{
A a[2] = { 0, 1 };
return 0;
}
It works perfectly. So here the MOVE constructor is used.
Let's remove the move constructor and add a copy one:
#include <iostream>
#include <memory>
using namespace std;
struct A {
unique_ptr<int> ref;
A(const A&a)
: ref( a.ref.get() ? new int(*a.ref) : nullptr )
{ }
A(A&&) = delete;
A(const int i) : ref(new int(i)) { }
~A() = default;
};
int main()
{
A a[2] = { 0, 1 };
return 0;
}
Now the compilation falls with the error "use of deleted function ‘A::A(A&&)’"
So the MOVE constructor is REQUIRED and there is no fall back to a COPY constructor.
Now let's remove both copy- and move-constructors:
#include <iostream>
#include <memory>
using namespace std;
struct A {
unique_ptr<int> ref;
A(const int i) : ref(new int(i)) { }
~A() = default;
};
int main()
{
A a[2] = { 0, 1 };
return 0;
}
And it falls with "use of deleted function ‘A::A(const A&)’" compilation error. Now it REQUIRES a COPY constructor!
So there was a fallback (?) from the move constructor to the copy constructor.
Why? Does anyone have any idea how does it conform to the C++ standard and what actually is the rule of choosing among copy/move constructors?
There is no "fallback". It is called overload resolution. If there are more than one possible candidate in overload resolution then the best match is chosen, according to a complicated set of rules which you can find by reading the C++ standard or a draft of it.
Here is an example without constructors.
class X { };
void func(X &&) { cout << "move\n"; } // 1
void func(X const &) { cout << "copy\n"; } // 2
int main()
{
func( X{} );
}
As-is: prints "move"
Comment out "1": prints "copy"
Comment out "2": prints "move"
Comment out "1" and "2": fails to compile
In overload resolution, binding rvalue to rvalue has higher preference than lvalue to rvalue.
Here is a very similar example:
void func(int) { cout << "int\n"; } // 1
void func(long) { cout << "long\n"; } // 2
int main()
{
func(1);
}
As-is: prints "int"
Comment out "1": prints "long"
Comment out "2": prints "int"
Comment out "1" and "2": fails to compile
In overload resolution, an exact match is preferred to a conversion.
In your three examples on this thread we have:
1: Two candidate functions; rvalue prefers rvalue (as in my first example)
A(const A&);
A(A&&); // chosen
2: Two candidate functions; rvalue prefers rvalue (as in my first example)
A(const A&);
A(A&&); // chosen
3: One candidate function; no contest
A(const A&); // implicitly declared, chosen
As explained earlier, there is no implicit declaration of A(A&&) in case 3 because you have a destructor.
For overload resolution it does not matter whether the function body exists or not, it is whether the function is declared (either explicitly or implicitly).
The function that it's going to use is chosen before it checks if it is deleted or not. In the case that the copy constructor was available and the move constructor was deleted, the move constructor was still the best choice out of the two. Then it sees that it's deleted and gives you an error.
If you had the same example but actually removed the move constructor, rather than making it deleted, you'll see that it compiles fine and does fall back to use the copy constructor:
#include <iostream>
#include <memory>
using namespace std;
struct A {
unique_ptr<int> ref;
A(const A&a)
: ref( a.ref.get() ? new int(*a.ref) : nullptr )
{ }
A(const int i) : ref(new int(i)) { }
~A() = default;
};
int main()
{
A a[2] = { 0, 1 };
return 0;
}
This class does not have a move constructor declared for it at all (not even implicitly), therefore it cannot be chosen.