Surprising behavior in multiple copy constructor inheritance - c++

Starting with C++11, there can be two copy constructors, one taking a parameter of type T&, and one taking a parameter of type const T&.
I have a situation where (seemingly) adding a second copy constructor causes neither one to get called, when the constructors are inherited in a derived class. The copy constructor is overridden by a templatized constructor when both are present.
Here is a MWE:
struct A {
template <typename... Args>
A (Args&&... args)
{ std::cout << "non-default ctor called\n"; }
A (A&) { std::cout << "copy ctor from non-const ref\n"; }
};
struct C :public A { using A::A; };
int main() {
C c1;
C c2(c1);
}
Running this code, we see output
non-default ctor called
copy ctor from non-const ref
which is as expected.
However, adding an additional constructor to struct A as follows:
A (const A&) { }
somehow causes the other copy constructor not to get called, so the output becomes
non-default ctor called
non-default ctor called
In my use case, I want to inherit all the constructors from a base class into a derived class, including the copy constructors and anything else. But it seems that somehow the two copy constructors don't get inherited when they are both present. What is going on here?

From https://en.cppreference.com/w/cpp/language/using_declaration
If one of the inherited constructors of Base happens to have the signature that matches a copy/move constructor of the Derived, it does not prevent implicit generation of Derived copy/move constructor (which then hides the inherited version, similar to using operator=).
So
struct C :public A { using A::A; };
is
struct C :public A
{
using A::A;
C(const C&) = default;
C(C&&) = default;
};
where C(const C&) = default; is similar to
C(const C& c) : A(static_cast<const A&>(c)) {}
So with
struct A {
template <typename... Args>
A (Args&&... args)
{ std::cout << "non-default ctor called\n"; }
A (A&) { std::cout << "copy ctor from non-const ref\n"; }
};
template constructor is chosen, but
struct A {
template <typename... Args>
A (Args&&... args)
{ std::cout << "non-default ctor called\n"; }
A (const A&) { std::cout << "copy ctor from const ref\n"; }
A (A&) { std::cout << "copy ctor from non-const ref\n"; }
};
A (const A&) is chosen.
As you can notice, there is also a defect:
The semantics of inheriting constructors were retroactively changed by a defect report against C++11. Previously, an inheriting constructor declaration caused a set of synthesized constructor declarations to be injected into the derived class, which caused redundant argument copies/moves, had problematic interactions with some forms of SFINAE, and in some cases can be unimplementable on major ABIs. Older compilers may still implement the previous semantics.
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0136r1.html
With that defect, your class C would be
struct C :public A
{
using A::A;
template <typename ...Ts>
C(Ts&&... ts) : A(std::forward<Ts>(ts)...) {} // Inherited.
C(const C&) = default;
C(C&&) = default;
};
So you call C(C& c) : A(c) {} (after template substitution).

Related

template constructor selected instead of copy constructor C++

Let following program:
#include <variant>
struct A{
A(){}
~A(){}
A(const A&){}
A& operator = (const A&) { return *this;}
A(A&&){}
A& operator = (A&& ) { return *this; }
using var = std::variant<int, float>;
var v;
template<typename T>
A(T&& t): v(std::forward<T>(t)){}
};
struct B
{
A m_a;
B( A a) : m_a(a) //calls template constructor instead of copy!
{}
};
Live example: godbold example
Q: Why template generic constructor chosen instead of copy (or move) constructor?
Edit : I think that, this is not duplicate of Why isn't this templated move constructor being called?
Because that issue asks why templated constructor NOT called?. That issue problem is - NOT called templated constructor.
I'm asking why templated constructor IS calling?.
My problem is -- on the contrary, calling templated constructor.
:)
It's just a better match. The template will instantiate a ctor that looks like:
A(T& t): v(t)){}
either pass as const&
https://godbolt.org/z/78bdzczE5
or create better matching ctor
https://godbolt.org/z/b8WvjW38T

why does adding a move constructor disable initializier list?

With a simple struct such as
struct Foo { int i; };
I can create a new instance using an initializer list; no need to write a constructor:
Foo foo { 314 };
If I now add a move constructor
struct Bar
{
int i;
Bar(Bar&& other) { i = other.i; }
};
The initializer no longer works and I have to add a constructor too:
Bar(int i) : i(i) {}
I'm guessing this behavior is somewhat related to this answer (for user-defined move-constructor disables the implicit copy-constructor?), but more details would be nice.
Edit: as indicated by the answers, this has to do with adding a constructor. Which in turn would seem to create an inconsistency of sorts, if I add just a move operator:
struct Baz
{
int i;
Baz& operator=(Baz&& other)
{
this->i = other.i;
return *this;
}
};
The initializer works again, although with a slightly different syntax for "move" (yes, this is actually default construct and move assignment; but the end result seems about the same):
Baz baz{ 3141 };
Baz b;
b = std::move(baz);
When there are no constructors this syntax is aggregate initialization because this structure is an aggregate.
When a constructor is added this structure is no longer an aggregate, aggregate initialization cannot be used. The exact rules are listed in list initialization, the relevant ones are:
The effects of list initialization of an object of type T are:
Otherwise, if T is an aggregate type, aggregate initialization is performed.
Otherwise, the constructors of T are considered, in two phases:...
It is not initializer list construction that is disabled by the move constructor (that was not present there to start with), but aggregate construction. And for a good reason: by adding a custom constructor, we indicate to the compiler exactly that the class in not an aggregate, that something different is necessary than just operate on each of its members one by one.
For an aggregate, the default default, copy, and move constructor will work well even if the member variables have nontrivial types. A copy construction will automatically be deleted if it can't be delegated to them, leaving move construction usable:
struct A { // non-copyable
int a;
int b;
A(int a_, int b_): a(a_), b(b_) { std::cout << "A(int,int)\n"; }
A() { std::cout << "A()\n"; }
A(const A&) = delete;
A(A&&) { std::cout << "A(A&&)\n"; }
};
struct B {
A a;
};
int main() {
B b1{{1,2}}; // OK: aggregate
B b2{std::move(b1)}; // OK: calls A::A(A&&)
//B b3{b1}; // error: B::B(const B&) auto-deleted
}
However, if you want to delete copy construction for some other reason and keep the others at default, just be explicit about it:
struct A { // copyable
int a;
int b;
A(int a_, int b_): a(a_), b(b_) { std::cout << "A(int,int)\n"; }
A() { std::cout << "A()\n"; }
A(const A&) { std::cout << "A(const A&)\n"; }
A(A&&) { std::cout << "A(A&&)\n"; }
};
struct B { // non-copyable
A a;
B() = default;
B(const B&) = delete;
B(B&&) = default;
};
int main() {
B b1{{1,2}}; // OK: still an aggregate
B b2{std::move(b1)}; // delegates to A::A(A&&)
//B b3{b1}; // error
}
Because you're using aggregate initialization, which says:
Aggregate initialization is a form of list-initialization, which
initializes aggregates An aggregate is one of the following types:
array type class type (typically, struct or union), that has
no private or protected non-static data members
no user-provided,
inherited, or explicit (since C++17) constructors (explicitly
defaulted or deleted constructors are allowed) (since C++11)
no
virtual, private, or protected (since C++17) base classes
no virtual
member functions
Point 2 makes your case fail.

Abstract classes and move semantics

According to “Rule Of Five” when I declare one of: copy or move operation or destructor I must write all of them, because the compiler doesn't generate them (some of them) for me. But if my class (A) derives from an abstract class with a virtual destructor, does this mean that the destructor in class A will be considered "user-defined"? As a consequence, will move semantics not work with objects of this class A because compiler won't generate a move constructor for me?
struct Interface {
virtual void myFunction() = 0;
virtual ~Interface() {};
};
struct A : public Interface {
void myFunction() override {};
};
In [class.copy]:
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
(9.1) — X does not have a user-declared copy constructor,
(9.2) — X does not have a user-declared copy assignment operator,
(9.3) — X does not have a user-declared move assignment operator, and
(9.4) — X does not have a user-declared destructor.
[ Note: When the move constructor is not implicitly declared or explicitly supplied, expressions that otherwise
would have invoked the move constructor may instead invoke a copy constructor. —end note ]
Interface does have a user-declared destructor, so Interface's move constructor will not be implicitly declared as defaulted. But A doesn't fit any of those bullet points - so it will have an implicit move constructor that is defaulted. A(A&& )will just copy the Interface part, as per the note. We can verify this by adding members to Interface and A:
#include <iostream>
template <char c>
struct Obj {
Obj() { std::cout << "default ctor" << c << "\n"; }
Obj(const Obj&) { std::cout << "copy ctor" << c << "\n"; }
Obj(Obj&&) { std::cout << "move ctor" << c << "\n"; }
};
struct Interface {
virtual void myFunction() = 0;
virtual ~Interface() {};
Obj<'I'> base;
};
struct A : public Interface {
void myFunction() override {};
Obj<'A'> derived;
};
int main() {
A a1; // default I, default A
std::cout << "---\n";
A a2(std::move(a1)); // copy I, move A
std::cout << "---\n";
A a3(a2); // copy I, copy A
}

Why do "normal" implementations of copy/move functions in derived classes behave differently depending on how they're defined?

I'm confused about the behavior I'm seeing when derived class copy and move functions call their base class versions.
I have a base class with various constructors that tell me when they're called:
#include <iostream>
class Base {
public:
Base() {}
template<typename T>
Base(T&&) { std::cout << "URef ctor\n"; }
Base(const Base&) { std::cout << "Copy ctor\n"; }
Base(Base& rhs): Base(const_cast<const Base&>(rhs))
{ std::cout << " (from non-const copy ctor)\n"; }
Base(Base&&) { std::cout << "Move ctor\n"; }
Base(const Base&& rhs): Base(rhs)
{ std::cout << " (from const move ctor)\n"; }
};
For a derived class with compiler-generated copy and move operations
class Derived: public Base {};
and this test code,
int main()
{
Derived d;
Derived copyNCLValue(d);
Derived copyNCRvalue(std::move(d));
const Derived cd;
Derived copyCLValue(cd);
Derived copyCRvalue(std::move(cd));
}
gcc 4.8.1 produces this output:
Copy ctor
Move ctor
Copy ctor
Copy ctor
This surprises me. I expected the base class constructor taking the universal reference to be called, because it can be instantiated to create an exact match on the derived object that is presumably passed from the derived class's functions. The base class copy and move functions require a derived-to-base conversion.
If I change the derived class to declare the copy and move functions myself, but to give them the default implementations,
class Derived: public Base {
public:
Derived(){}
Derived(const Derived& rhs) = default;
Derived(Derived&& rhs) = default;
};
gcc produces the same output. But if I write the functions myself with what I believe are the default implementations,
class Derived: public Base {
public:
Derived(){}
Derived(const Derived& rhs): Base(rhs) {}
Derived(Derived&& rhs): Base(std::move(rhs)) {}
};
I get the output I originally expected:
URef ctor
URef ctor
URef ctor
URef ctor
I'd expect to get the same output in each case. Is this a bug in gcc, or is there something I'm not understanding?
This surprises me. I expected the base class constructor taking the universal reference to be called, because it can be instantiated to create an exact match on the derived object that is presumably passed from the derived class's functions. The base class copy and move functions require a derived-to-base conversion.
No. The compiler sees the line Derived copyCRvalue(std::move(cd)); that really means Derived copyCRvalue(static_cast<const Derived&&>(cd)); and it tries to find a constructor in Derived that matches that call. It finds two closely related constructors both implicitly declared:
Derived(Derived const &); // copy constructor
Derived(Derived &&); // move constructor
The second one cannot be used, since the rvalue-reference is to a const object, but the first one is a match. The definition of the implicitly defined copy constructor is:
Derived(Derived const &rhs) : base(static_cast<Base const &>(rhs)) {}
Inside the constructor, the rhs is an lvalue, not an rvalue (and a templated constructor is not a copy-constructor anyways).
But if I write the functions myself with what I believe are the default implementations,
class Derived: public Base {
public:
Derived(){}
Derived(const Derived& rhs): Base(rhs) {}
Derived(Derived&& rhs): Base(std::move(rhs)) {}
};
Except that those are not the definitions that the compiler will provide. The specific quota from the standard is in 12.8/15
The implicitly-defined copy/move constructor for a non-union class X performs a memberwise copy/move of its bases and members.
That is, the implicitly defined constructor will initialize the destination's base with the source's base, and similarly each member in the destination with the same member in the source.

Danger with virtual base move assignment operators when they are now allowed to be used?

This concerns the resolution of C++ Issue http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1402 . Summary:
template<typename T>
struct wrap
{
wrap() = default;
wrap(wrap&&) = default;
wrap(const wrap&) = default;
T t;
};
struct S {
S(){}
S(const S&){}
S(S&&){}
};
typedef wrap<const S> W;
// Error, defaulted move constructor of "wrap<const S>" is deleted!
W get() { return W(); }
(The issue is that we are getting an error for this snippet, even though the compiler could simply use the copy constructor of "S", as it does when the user explicitly writes the move constructor of "wrap". The issue was resolved to ignore deleted move constructors (and assignment operators) during overload resolutions, hence using the copy constructor above as desired.)
When the resolution was drafted, the following comment was made about this resolution:
There are no additional restrictions for implicit/defaulted move operations relative to copy. This means that move assignment in a virtual base is dangerous (and compilers should probably warn) [...] But we decided in Batavia that we weren't going to preserve all C++03 code against implicit move operations, and I think this resolution provides significant performance benefits.
Can someone please describe what the concern with virtual base move assignment operators is?
Consider:
#include <iostream>
struct A
{
A() = default;
A(const A&) = default;
A(A&&) = default;
A& operator=(const A&) {std::cout << "operator=(const A&)\n"; return *this;}
A& operator=(A&&) {std::cout << "operator=(A&&)\n"; return *this;}
};
struct B
: virtual public A
{
};
struct C
: virtual public A
{
};
struct D
: public B,
public C
{
};
int
main()
{
D d1, d2;
d1 = std::move(d2);
}
I believe this program should output:
operator=(A&&)
operator=(A&&)
For me it actually outputs:
operator=(const A&)
operator=(const A&)
But I think this is just a clang bug (compiled with -std=c++1y). If I am correct about what the output should be, then the danger is that the move assignment operator is called twice. This is harmless (though potentially expensive) with the copy assignment operator, but not with the move assignment operator.