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.