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

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

提问人:Sedenion 提问时间:2/7/2022 最后编辑:cigienSedenion 更新时间:2/7/2022 访问量:650

问:

我偶然发现了下面一段代码。该案例在 MSVC 上产生的结果与在 clang 或 gcc 上产生的结果不同。也就是说,clang 13 和 gcc 11.2 调用 的复制构造函数,而 MSVC v19.29 调用模板化构造函数。我正在使用 C++17。"DerivedFoo"Foo

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

代码 (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
C++ 模板 C++17 language-lawyer copy-constructor

评论

0赞 doug 2/7/2022
如果使用 /permissive- 选项强制 c++17 合规性,则 MSVC 的行为正常。看起来像是旧代码的遗留物,可能会与一致的编译器一起破坏。
0赞 Sebastian 2/7/2022
@doug 这些行为(没有许可)是否公开记录在案?
0赞 doug 2/7/2022
不知道该特定问题是否已得到解决。Microsoft 有一长串合规性差异以及如何升级旧代码。
0赞 Sedenion 2/7/2022
@doug我在 C++17 和 C++20 中使用 MSVC 编译了代码并得到了上述结果(即 MSVC 调用模板化构造函数)。我使用了 VS2019(cl.exe版本 19.29.30139)。/permissive-
1赞 user17732522 2/7/2022
@Sedenion 缺陷报告似乎未在 19.30 之前实施:godbolt.org/z/sjv6h74M4

答:

11赞 user17732522 2/7/2022 #1

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

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

因此,隐式复制构造函数将通过重载解析来选择DerivedFoo

DerivedFoo d2(d1);

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

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

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

评论

0赞 Sedenion 2/7/2022
谢谢!但是,“即使它是从构造函数模板实例化的”是什么意思?我的意思是,“它”指的是什么?或者,如果我阅读了 C++ 标准中提到的部分以及您正确编写的内容,当模板化构造函数在实例化后“看起来像复制构造函数”时,它被明确排除(引用标准:“包括从模板实例化的此类构造函数”)?但是,如果实例化的构造函数“看起来”不像复制/移动构造函数,它是否包含在候选函数集中?
0赞 user17732522 2/7/2022
@Sedenion“”指的是“继承的构造函数”。是的,我认为你的解释是正确的。
0赞 Sedenion 2/8/2022
我明白了,谢谢!出于好奇,您是否碰巧知道 CWG 2356 的原因,即为什么这些构造函数没有被继承?
0赞 user17732522 2/8/2022
@Sedenion 不能肯定地说出他们的动机,但我想如果你写,你希望使用派生类的复制构造函数,如果基地中一些不知道派生类的模板构造函数可以覆盖它,那将是令人惊讶的。请注意,拥有不受约束的单参数模板构造函数本身就已经是一个问题,因为它会扰乱复制和移动构造,如您的示例所示。您应该对其进行约束,以便模板参数不能是 .DerivedFoo d2(d1);FooFoo
0赞 Sedenion 2/8/2022
是的,我计划通过类似的东西来限制模板构造函数(这就是实现的方式)。但后来我遇到了 MSVC 和 clang 的不同结果:MSVC 调用了模板构造函数,尽管因为,嗯,与 .我想这个案例可能是CWG 2356背后的原因之一。再次感谢!std::enable_if_t<!std::is_same_v<std::remove_cv_t<std::remove_reference_t<U>>, Foo>>std::optionalDerivedFoo d2(d1)enable_ifDerivedFooFoo