While pursuing some errors, I stumbled upon the following behavior of initialization, which seems odd to me: While initialization checks for existing constructors, there seem to be cases were templates for fitting constructors are ignored. Consider for example the following program:
#include <iostream>
template<class T>
struct A {
A() {};
template<class S>
A(const A<S>& a) {std::cout << "constructor template used for A" << std::endl;};
};
template<class T>
struct B{
B() {};
B(const B<int>& b) {std::cout << "constructor used for B" << std::endl;};
};
int main() {
A<int> a;
B<int> b;
A<int> aa = a;
B<int> bb = b;
A<double> aaa = a;
}
For me, this produces the output
constructor used for B
constructor template used for A
this means it does not use the constructor in the third line of main. Why not? Is there a reason? Or is my syntax off somewhere? The template seems to work, as it is successfully used in the last line.
I know the example seems overly complicated, but various simplifications made the behavior I wanted to display go away. Also: A template specialization will be used by the initialization and is how I currently prevent this causing errors (where it caused errors in the first place).
I am sorry if my question is off in any way, I am not a programmer, I am not a native speaker and this is my first question, please forgive me.
The compiler provides an implicitly declared non-template copy constructor with signature equivalent to
A(const A& a);
because a template constructor is not considered as a user defined copy constructor, i.e. a copy constructor has to be a non-template.
The implicitly declared copy constructor is a better match in the overload resolution than the template version, and is the one that gets called when you copy construct an A<T> from an A<T>. This can be illustrated with a simple example, with a user defined A(const A&):
#include <iostream>
template<class T>
struct A
{
A() {};
A(const A& a) {
std::cout << "copy constructor used for A" << std::endl;
}
template<class S>
A(const A<S>& a) {
std::cout << "constructor template used for A" << std::endl;
}
};
int main()
{
A<int> ai;
A<double> ad = ai; / calls template conversion contructor
A<int> ai2 = ai; // calls copy constructor A(const A&);
}
Per Paragraph 12.8/7 of the C++11 Standard:
If the class definition does not explicitly declare a copy constructor, one is declared implicitly.
Moreover, per Paragraph 12.8/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).
Therefore, the compiler will generate an implicit copy constructor here that gets invoked during the execution of this line:
A<int> aa = a;
This explains why you do not see a corresponding output.
This is because templated ctor is not a copy ctor. See also https://stackoverflow.com/a/1249848/341065.
Related
I stumbled over the following piece of code. The "DerivedFoo" case produces different results on MSVC than on clang or gcc. Namely, clang 13 and gcc 11.2 call the copy constructor of Foo while MSVC v19.29 calls the templated constructor. I am using C++17.
Considering the non-derived case ("Foo") where all compilers agree to call the templated constructor, I think that this is a bug in clang and gcc and that MSVC is correct? Or am I interpreting things wrong and clang/gcc are correct? Can anyone shed some light on what might be going on?
Code (https://godbolt.org/z/bbjasrraj):
#include <iostream>
using namespace std;
struct Foo {
Foo() {
cout << "\tFoo default constructor\n";
}
Foo(Foo const &) { cout << "\tFoo COPY constructor\n";
}
Foo(Foo &&) {
cout << "\tFoo move constructor\n";
}
template <class U>
Foo(U &&) {
cout << "\tFoo TEMPLATED constructor\n";
}
};
struct DerivedFoo : Foo {
using Foo::Foo;
};
int main() {
cout << "Foo:\n";
Foo f1;
Foo f2(f1);
cout << "\nConst Foo:\n";
Foo const cf1;
Foo cf2(cf1);
cout << "\nDerivedFoo:\n";
DerivedFoo d1;
DerivedFoo d2(d1);
cout << "\nConst DerivedFoo:\n";
DerivedFoo const cd1;
DerivedFoo cd2(cd1);
}
Result for clang and gcc:
Foo:
Foo default constructor
Foo TEMPLATED constructor
Const Foo:
Foo default constructor
Foo COPY constructor
DerivedFoo:
Foo default constructor
Foo COPY constructor <<<<< This is different
Const DerivedFoo:
Foo default constructor
Foo COPY constructor
Result for MSVC:
Foo:
Foo default constructor
Foo TEMPLATED constructor
Const Foo:
Foo default constructor
Foo COPY constructor
DerivedFoo:
Foo default constructor
Foo TEMPLATED constructor <<<<< This is different
Const DerivedFoo:
Foo default constructor
Foo COPY constructor
It is correct that the constructor template is generally a better match for the constructor call with argument of type DerivedFoo& or Foo& than the copy constructors are, since it doesn't require a const conversion.
However, [over.match.funcs.general]/8 essentially (almost) says, in more general wording, that an inherited constructor that would have the form of a move or copy constructor is excluded from overload resolution, even if it is instantiated from a constructor template. Therefore the template constructor will not be considered.
Therefore the implicit copy constructor of DerivedFoo will be chosen by overload resolution for
DerivedFoo d2(d1);
and this will call Foo(Foo const &); to construct the base class subobject.
This wording is a consequence of CWG 2356, which was resolved after C++17, but I think it is supposed to be a defect report against older versions as well. (I don't really know though.)
So GCC and Clang are correct here. Also note that MSVC behaves according to the defect report as well since version 19.30 if the conformance mode (/permissive-) is used.
Take for example this code:
#include <type_traits>
#include <iostream>
struct Foo
{
Foo() = default;
Foo(Foo&&) = delete;
Foo(const Foo&) noexcept
{
std::cout << "copy!" << std::endl;
};
};
struct Bar : Foo {};
static_assert(!std::is_move_constructible_v<Foo>, "Foo shouldn't be move constructible");
// This would error if uncommented
//static_assert(!std::is_move_constructible_v<Bar>, "Bar shouldn't be move constructible");
int main()
{
Bar bar {};
Bar barTwo { std::move(bar) };
// prints "copy!"
}
Because Bar is derived from Foo, it doesn't have a move constructor. It is still constructible by using the copy constructor. I learned why it chooses the copy constructor from another answer:
if y is of type S, then std::move(y), of type S&&, is reference compatible with type S&. Thus S x(std::move(y)) is perfectly valid and call the copy constructor S::S(const S&).
—Lærne, Understanding std::is_move_constructible
So I understand why a rvalue "downgrades" from moving to a lvalue copying, and thus why std::is_move_constructible returns true. However, is there a way to detect if a type is truly move constructible excluding the copy constructor?
There are claims that presence of move constructor can't be detected and on surface they seem to be correct -- the way && binds to const& makes it impossible to tell which constructors are present in class' interface.
Then it occurred to me -- move semantic in C++ isn't a separate semantic... It is an "alias" to a copy semantic, another "interface" that class implementer can "intercept" and provide alternative implementation. So the question "can we detect a presence of move ctor?" can be reformulated as "can we detect a presence of two copy interfaces?". Turns out we can achieve that by (ab)using overloading -- it fails to compile when there are two equally viable ways to construct an object and this fact can be detected with SFINAE.
30 lines of code are worth a thousand words:
#include <type_traits>
#include <utility>
#include <cstdio>
using namespace std;
struct S
{
~S();
//S(S const&){}
//S(S const&) = delete;
//S(S&&) {}
//S(S&&) = delete;
};
template<class P>
struct M
{
operator P const&();
operator P&&();
};
constexpr bool has_cctor = is_copy_constructible_v<S>;
constexpr bool has_mctor = is_move_constructible_v<S> && !is_constructible_v<S, M<S>>;
int main()
{
printf("has_cctor = %d\n", has_cctor);
printf("has_mctor = %d\n", has_mctor);
}
Notes:
you probably should be able to confuse this logic with additional const/volatile overloads, so some additional work may be required here
doubt this magic works well with private/protected constructors -- another area to look at
doesn't seem to work on MSVC (as is tradition)
How to find out whether or not a type has a move constructor?
Assuming that the base class comes from the upstream, and the derived class is part of your application, there is no further decision you can make, once you decided to derive 'your' Bar from 'their' Foo.
It is the responsibility of the base class Foo to define its own constructors. That is an implementation detail of the base class. The same is true for the derived class. Constructors are not inherited. Trivially, both classes have full control over their own implementation.
So, if you want to have a move constructor in the derived class, just add one:
struct Bar : Foo {
Bar(Bar&&) noexcept {
std::cout << "move!" << std::endl;
};
};
If you don't want any, delete it:
struct Bar : Foo {
Bar(Bar&&) = delete;
};
If you do the latter, you can also uncomment the second static_assert without getting an error.
I have the following code:
#include <iostream>
#include <utility>
class A {
public:
A() { }
A(const A&, int i) { std::cout << "A copy" << std::endl; }
A(A&&, int i) { std::cout << "A move" << std::endl; }
A(const A&) = delete;
A(A&&) = delete;
};
class B : public A {
public:
B(int i) { std::cout << "B construct" << std::endl; }
B(const A& a) : A(a, 1) { std::cout << "B copy" << std::endl; }
B(A&& a) : A(std::move(a), 1) { std::cout << "B move" << std::endl; }
};
B make_b() {
return B(1);
}
int main() {
B b = make_b();
}
The compiler error reports the error that B cannot be copy-constructed (for return from make_b), because it has no copy-constructor, because A's copy-constructor is deleted.
Does B(const A&) not qualify as copy-constructor, and what is the rule that applies here?
Does the copy and move constructor always have to take one argument of the same type (and not a superclass)?
Can it have additional parameters with default values?
Can it be templated so that it can resolve to a valid copy constructor?
To allow implicit copy and move construction, is it necessary to explicitly add copy and move-constructors B(const B&) and B(B&&)?
Does B(const A&) not qualify as copy-constructor, and what is the rule that applies here?
No. A copy constructor creates another object of the same type. A is not the same type as B. If you try to construct an object of a derived class from an object of its base class, how are you supposed to initialize the derived class' members? The source object you are copying from doesn't have those members to copy!
Furthermore, B already has a copy constructor, implicitly declared by the compiler, but because the implicit definition would be ill-formed (because the base class A is not copyable) it is deleted by the compiler, so you cannot use it.
Does the copy and move constructor always have to take one argument of the same type (and not a superclass)?
Not necessarily one argument, B(const B&, int = 0) is a copy constructor, because it can be called to create a copy of a B. But B(const A&) is not a copy constructor.
Can it have additional parameters with default values?
Yes.
To allow implicit copy and move construction, is it necessary to explicitly add copy and move-constructors B(const B&) and B(B&&)?
Yes, you need to define them explicitly, because the implicit definitions the compiler would use won't work.
Since your derived type doesn't have any members, and you already have constructors that take an A, you could define them like so:
B(const B& b) : B(static_cast<const A&>(b) { }
B(B&& b) : B(static_cast<A&&>(b) { }
This creates delegating constructors which simply forward the argument to your existing constructors (using suitable casts to the base type).
About 1 :
when constructing , the compiler calls all the base classes one by one from the highest until the currently-constructed-class.
if C inherits from B inherits from A
the compiler calls A() then B() than C() ctros in order to build C object.
the same goes for copy constructors:
in your example , you called for A() copy constructor to build the "A" part of the object , but you deleted it .
the problem here is returing B by value, which calls first move ctor if exits , and move ctor if not. you deleted both
The following code forwards constructors from base to derived class.
Why 2 copy constructor calls? What happen in the background?
Compiled with g++.
#include <iostream>
using namespace std;
struct A {
A() { cout << "A" << endl; }
A(const A&) { cout << "A(const A&)" << endl; }
template<typename T> A(T a); // Needed to compile :-O
};
template<typename T>
struct C : public T { using T::T; };
int main()
{
A a;
C<A> ca(a);
//C<A> caa(ca);
return 0;
}
Output is:
A
A(const A&)
A(const A&)
By defining a constructor template in A, C will be given a constructor template with a similar signature. It is implicitly defined similarly to:
template<typename T>
struct C : public T
{
//using T::T;
C() = default;
C(C const&) = default;
template<typename U> C(U a) : T( std::forward<U>(a) ) {}
};
This now calls the copy-constructor of A twice: once for taking the argument by value. The second call results from T( std::forward<U>(a) ) calling the copy-ctor of A. This was surprising to me, as you'd expect an inherited ctor to call the exact ctor of the base class of which it has been inherited. But that's not the case, overload resolution selects not the ctor template of A, but the plain copy-ctor A(A const&) (see below).
Interestingly, it doesn't care much what the constructor template in A does, it only needs to be declared. That's why in the OP, the definition can be missing; it can also be deleted (which might be a defect?).
The copy-ctor of A only has to be chosen during overload resolution of the initialization T( std::forward<U>(a) ). This is the case here: The argument is an rvalue of type A, which can bind directly to a const A& reference, as required by the copy-ctor of A. As the reference binding is direct and w/o derived-to-base conversion, the copy-ctor ranks as an Exact Match. The ctor template in A is ranked as an Exact Match as well, but as there's a template- and non-template function with the same rank in the overload set, the non-template function is preferred (the copy-ctor A(A const&)).
One call to A::A(const A&) is the base class constructor of C<A>.
The other is called to copy the pass-by-value parameter.
struct A{
virtual void what() { cout << "Base" << endl; };
};
struct B : A {
virtual void what() { cout << "Sub" << endl; };
int m;
B() : m(10) {};
B(const A & x) : m(50) {};
};
void main() {
B b1;
B b2 = b1;
cout << "Number: "
<< b2.m << endl;
};
Why isn't b2.m = 50? I'm trying to copy a b-object and i have the copy constructor B(const A & x) : m(50). Do i need need to make a copy c'tor for the derived class ass well ? Like B(const B&x) ?? I thought since a b-object has an a part, we could use B(const A & x) : m(50) instead of the default constructor: :S
In the case where you have a function with the parameter of an A object, you can send in a B object. How come it differs with the copy constructor?
The reason is that B(const A& x) is not a copy-ctor — copy constructor for type T must always take an lvalue reference to T as the first (and have no other non-default arguments) argument. Your class has no defined copy-ctor, so the compiler generates the default one, which does a member-wise copy, hence why b2.m is the same as b1.m.
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).
Copy constructors need to be of the same type.
You haven't actually made a copy constructor. B(const A & x) is just a constructor that takes a const A by reference.
So, you don't need to make one for the derived class "as well", but "at all". As you stated, the type of this will be B(const B &x).
The default copy-ctor for classes of type B would be B(const B&). Since you haven't specified this one, the compiler kindly generates it for you.
Its different with user defined methods as those cannot be compiler generated.