对于基类中的模板化构造函数,clang/gcc 和 MSVC 之间的结果不同

Gug*_*ugi 12 c++ templates copy-constructor language-lawyer c++17

我偶然发现了下面的代码。该"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);
}
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

use*_*522 11

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

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

DerivedFoo因此,将通过重载决策选择隐式复制构造函数

DerivedFoo d2(d1);
Run Code Online (Sandbox Code Playgroud)

这将调用Foo(Foo const &);构造基类子对象。

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

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