clang/gcc 和 MSVC 对基础 class 中的模板化构造函数的不同结果

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

我无意中发现了以下代码。 "DerivedFoo" 案例在 MSVC 上产生的结果与在 clang 或 gcc 上产生的结果不同。即,clang 13 和 gcc 11.2 调用 Foo 的复制构造函数,而 MSVC v19.29 调用模板构造函数。我正在使用 C++17。

考虑到所有编译器都同意调用模板构造函数的非派生情况 ("Foo"),我认为这是 clang 和 gcc 中的错误,而 MSVC 是正确的吗?还是我解释错了,而 clang/gcc 是正确的?任何人都可以阐明可能发生的事情吗?

代码(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);
}

clang 和 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

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

与复制构造函数相比,构造函数模板通常更适合参数类型为 DerivedFoo&Foo& 的构造函数调用,这是正确的,因为它不需要 const转换。

但是,[over.match.funcs.general]/8 本质上(几乎)用更一般的措辞说,具有移动或复制构造函数形式的继承构造函数被排除在重载决策之外,即使它是从中实例化的构造函数模板。因此不考虑模板构造器。

因此 DerivedFoo 的隐式复制构造函数将由

的重载决议选择
DerivedFoo d2(d1);

这将调用 Foo(Foo const &); 来构建基础 class 子对象。

这个措辞是 CWG 2356 的结果,它在 C++17 之后得到解决,但我认为它也应该是针对旧版本的缺陷报告。 (虽然我真的不知道。)

所以 GCC 和 Clang 在这里是正确的。另请注意,如果使用一致性模式 (/permissive-),MSVC 也会根据缺陷报告进行操作,并且自版本 19.30 起也是如此。