【问题标题】:Different results between clang/gcc and MSVC for templated constructor in base class对于基类中的模板化构造函数,clang/gcc 和 MSVC 之间的结果不同
【发布时间】:2022-02-06 18:25:15
【问题描述】:

我偶然发现了以下代码。 "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

【问题讨论】:

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

标签: c++ templates c++17 language-lawyer copy-constructor


【解决方案1】:

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

p>

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

因此,DerivedFoo 的隐式复制构造函数将由重载决议选择

DerivedFoo d2(d1);

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

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

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

【讨论】:

  • 谢谢!但是“即使它是从构造函数模板实例化的”是什么意思?我的意思是,“它”指的是什么?或者,如果我阅读了 C++ 标准中提到的部分以及您正确编写的内容,则模板化的构造函数在它被实例化后“看起来像一个复制构造函数”时被显式排除(引用标准:“包括从模板”)?但如果实例化的构造函数“看起来不像”复制/移动构造函数,它是否包含在候选函数集中?
  • @Sedenion "it" 指的是“继承的构造函数”。是的,我认为你的解释是正确的。
  • 我明白了,谢谢!出于好奇,您是否知道 CWG 2356 的原因,即为什么这些构造函数不被继承?
  • @Sedenion 不能确定他们的动机,但我想如果你写DerivedFoo d2(d1);,你希望使用派生类的复制构造函数,如果一些模板构造函数会令人惊讶在不知道派生类的基础中可以覆盖它。请注意,拥有不受约束的单参数模板构造函数本身已经是一个问题,因为它与您的 Foo 示例中的复制和移动构造相混淆。你应该限制它,使模板参数不能是Foo
  • 是的,我计划通过类似于std::enable_if_t&lt;!std::is_same_v&lt;std::remove_cv_t&lt;std::remove_reference_t&lt;U&gt;&gt;, Foo&gt;&gt; 的方式来限制模板构造函数(这就是std::optional 实现的方式)。但后来我用 MSVC 遇到了不同的结果,并为DerivedFoo d2(d1) 发出了叮当声:尽管enable_if,MSVC 调用了模板构造函数,因为DerivedFooFoo 不同。我想这个案例可能是 CWG 2356 背后的原因之一。再次感谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-11-02
  • 1970-01-01
  • 2021-04-23
  • 1970-01-01
  • 1970-01-01
  • 2020-10-27
  • 1970-01-01
相关资源
最近更新 更多