Home > Enterprise >  Different results between clang/gcc and MSVC for templated constructor in base class
Different results between clang/gcc and MSVC for templated constructor in base class

Time:02-08

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

CodePudding user response:

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.

  •  Tags:  
  • Related