【问题标题】:Apparently erroneous initialization of class template member compiles just fine显然错误的类模板成员初始化编译就好了
【发布时间】:2021-08-12 19:15:00
【问题描述】:

考虑以下 MCVE 代码:

struct Id { static inline int id(int c) {return c;} };

template <class C>
class Foo {
    C m_bar;
public:
    Foo() = default;
    Foo(int) : m_bar{bar} {} // Fails in clang, compiles in gcc/msvc
    Foo(int, int) : m_bar(bar) {} // Compiles with all gcc/clang/msvc
    Foo(int, int, int) : m_bar(Id::id(bar)) {} // Compiles in gcc/msvc with both () and {} syntax, fails for both in clang

    C bar() const { return m_bar; }
};

为简单起见,假设我实例化了Foo&lt;int&gt; x;。我希望所有的构造函数都会产生编译错误,因为我正在尝试用bar 初始化m_bar,这是一个方法(并不是我没有在这里调用bar) .

但所有变体都可以在所有主要编译器上编译,唯一的例外是 Foo(int) 中的 clang。

请注意,当我调用相应的构造函数重载时,所有编译器都会按预期失败,例如Foo&lt;int&gt; f{1};。但据我了解,class 是一个模板,而不是 构造函数,所以我希望所有构造函数在实例化类模板时都会失败,即使是未使用的构造函数.

有趣的是,当使用如下所示的非模板类时,所有三个构造函数都按预期失败:

class Foo {
    int m_bar;
public:
    Foo() = default;
    Foo(int) : m_bar{bar} {} // fails in all gcc/clang/msvc
    Foo(int, int) : m_bar(bar) {} // fails in all gcc/clang/msvc
    Foo(int, int, int) : m_bar{Id::id(bar)} {} // fails in all gcc/clang/msvc

    int bar() const { return m_bar; }
};

为方便起见,这里是带有编译器输出的godbolt 链接。

我的问题是:这是所有三个编译器中的一个错误,还是我这边有一些误解,也许是标准中解释这种行为的一些晦涩的部分?

【问题讨论】:

  • 你能在这篇文章中添加一个minimal reproducible example 的代码和它给你的错误吗?不是很多人喜欢点击链接并不得不编辑代码来尝试和预先产生问题。
  • #if 1 是否意味着切换错误?我尝试了#if 0#if 1,所有编译器都编译得很好。
  • 另外,那个godbolt链接不会永远存在,如果链接上的信息是理解问题所必需的,那么这个问题在不久的将来就会“无用”。
  • @NathanOliver:这实际上可重现的例子。使用快速命令编译 sn-p 就足够了。 Foo 的实例化是可选的。这是我缺少的错误消息,所以也没有太多可显示的。如果有帮助,我可以用语言律师标签标记它。
  • 我不确定为什么只有 clang 会捕获它,但请尝试使用其他构造函数之一(例如 Foo(5) 此处为 godbolt.org/z/c6codf1x5)。全部失败

标签: c++ visual-c++ g++ clang language-lawyer


【解决方案1】:

类模板成员是按需实例化的。见[temp.inst]/9

实现不应隐式实例化函数模板、变量模板、成员模板、非虚拟成员函数、成员类、类模板的静态数据成员或constexpr if 语句的子语句,除非需要这种实例化。

另外,由于m_bar 依赖于模板参数,表达式m_bar{bar} 在模板解析期间无法进行语法检查。

【讨论】:

  • 非常有趣,感谢您指出那段。所以在我看来这是clang中的一个错误?该标准明确规定“不得”,即使根本没有实例化,clang 也会失败。此外,只有在使用统一初始化语法时才会失败(m_bar(bar) 编译得很好),这至少是不一致的(显然缩小在这里没有任何作用)。
  • 嗯,clang 说error: reference to non-static member function must be called,这是真的,bar 是一个非静态成员,所以它只能在调用中显示为不合格。 Clang 在这里只是提供帮助,毕竟代码没有机会工作。
  • 这是我的想法,实际上是因为clang,我在一些多年未触及的代码中发现了这样一个错误。老实说,我更喜欢这种诊断方法,因为它揭示了一个真正的问题。但严格意义上来说,不符合标准就可以称之为bug吗?
  • 它是 C++17 中的 [temp.res]/8,它简单地说“知道哪些名称是类型名称,就可以检查每个模板的语法。”,其余的几乎相同。
【解决方案2】:

这是因为类模板和构造函数模板之间的唯一区别是类模板在整个类中是全局的,但是构造函数模板对整个类不是全局的,它们在作用域的末尾被删除。每个成员函数也被单独实例化,因此永远不会调用有错误的构造函数。人们可能使用模板化构造函数而不是类模板的主要原因是因为他们可能需要一些非模板构造函数。

您还可以查看this 了解有关类模板和构造函数模板的更多信息。

【讨论】:

  • 感谢您的回答,但我没有使用构造函数模板
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-14
相关资源
最近更新 更多