【问题标题】:constexpr defining static data member of literal type that is declared constconstexpr 定义声明为 const 的文字类型的静态数据成员
【发布时间】:2017-10-13 09:12:44
【问题描述】:

我有一个关于 constexpr 定义文本类型的静态数据成员的问题,该成员在类定义中声明为 const(而不是指定 inline 或 constexpr):

// S.h

struct S
{
  static int const i; // not specified inline or constexpr
};

// S.cpp

#include "S.h"
constexpr int const S::i = 42; // definition, not declaration

// main.cpp

#include "S.h"
int main()
{
  return S::i;
}

Clang/gcc 在 C++11/14 模式下返回 42,但在 C++17 模式下报错(未定义对 S::i 的引用)。如果我注释掉 constexpr 在 C++17 模式下也都返回 42。

S::i 具有外部链接,因为 S 具有外部链接。 S::i 未声明为 constexpr,因此(如果我没记错的话)C++17 10.1.5 p1 不适用:

使用 constexpr 声明的函数或静态数据成员 说明符隐式是内联函数或变量

我理解这句话的意思是(大胆的理解): 在类定义中使用 constexpr 说明符声明的静态数据成员隐式是内联变量

S::i 因此不是内联变量。 然而,S::i 的定义似乎在 C++17 模式下具有内部链接,就好像 constexpr 表示内联一样。它是否正确?如果是这样,标准中的证明在哪里?

还是我误解了 10.1.5 p1,它的真正意思是(大胆的误解): 在类定义和命名空间范围内的定义中使用 constexpr 说明符声明的静态数据成员隐式是内联变量?

【问题讨论】:

    标签: c++ gcc clang c++17


    【解决方案1】:

    [dcl.inline]/6 状态:

    如果具有外部链接的函数或变量被声明为内联 一个翻译单元,应在所有翻译中声明为内联 它出现的单位;不需要诊断。

    所以正如你所指出的,如果我们可以证明constexpr 隐含暗示inline,它可以解释你的示例的未定义​​引用错误。

    [dcl.constexpr]/1 声明 [强调我的]:

    constexpr 说明符应仅应用于定义 变量或变量模板或函数的声明或 函数模板。

    还有:

    使用 constexpr 说明符声明的函数或静态数据成员 隐式是内联函数或变量 ([dcl.inline])。

    [basic.def]/1 声明 [强调我的]:

    声明可以将一个或多个名称引入翻译单元 或重新声明先前声明引入的名称

    还有(/2):

    声明就是定义,除非:

    [...没有一个适用于constexpr int const S::i = 42;]

    这里的本质是定义是声明(完全定义了声明引入的实体),所以constexpr int const S::i = 42;也是(除了定义之外)一个(重新)声明, 在这种情况下 [dcl.constexpr]/1 适用,并且S::iS.cpp 的翻译单元中是内联的,因此,由 [dcl.inline]/6 在它出现的所有其他翻译单元中。相反,通过 [dcl.constexpr]/1,constexpr 说明符,例如特别是在这种静态数据成员的上下文中,只能出现在作为定义的声明中。

    在后者的上下文中有些相关的是,带有初始化的 constexpr 静态数据成员声明也是,从 C++17 开始,一个定义允许 constexpr 仅适用于的规范变量定义(即,永远不要使用非初始化声明)。见[depr.static_constexpr]/1

    【讨论】:

    • 感谢您的解释。这意味着我可以回答我自己的问题:是的,我误解了 10.1.5 p1 s2。但有两件事困扰着我。首先,标准委员会引入了一个新的语言特性(内联变量),它破坏了最多 6 年的现有代码。其次,它与声明为 extern 的变量不一致。假设在 S.h 中改为“extern int const i;”,在 S.cpp 中改为“constexpr int const i = 42;”而在 main() 中,改为“return i;”。这没关系,没有对 i 的未定义引用。这同样适用于声明为 extern 的变量模板。很奇怪。
    • @xy 我同意突破性的变化总是微妙的。至于你的第二条评论,我没有看到任何不一致/奇怪的地方。 extern 在该上下文中只是存储类说明符,不适用于类成员(参见 [[dcl.stc]/5](timsong-cpp.github.io/cppwp/dcl.stc)。作者 [[basic.link]/3](timsong-cpp.github.io/cppwp/basic.link) @987654341 @ in S.h 有外部链接,对于这个非类成员变量,constexpr 不(隐含)暗示inline,因此没有不一致,i 的外部链接,正如预期的那样,允许它用于main()
    • 但是您确切地提到了我的意思的不一致:“ constexpr 不(隐式)暗示内联”。当 constexpr 说明符在命名空间范围内声明为 extern 的 const 变量的定义中不暗示内联时,为什么 constexpr 说明符在命名空间范围内的 const 静态数据成员的定义中暗示内联? S::i 实际上只是一个类范围绑定的普通命名空间范围“extern int const i”。除此之外,我看不出这两者之间有什么区别。这就是为什么我认为 constexpr 不应该暗示 S::i 的内联。
    • @xy 我回答了您的评论,将 "inconsistency" 解释为 "ambiguous" 意义上的(这里没有歧义),而我知道您指的是用于分离(尽管相似)实体的明确定义的规则之间的意图不一致。至于后者,我确实明白你的意思,但我无法回答标准为什么选择这种方法(只是上面的例子确实是定义明确的,并且符合标准)。对于这样的问题,您可能最好在更接近实际使用和编写标准的邮件组/松弛 (cpplang) 中提问。
    • @xy N4424 及其更新/附录P0386R0 应该为您提供为什么的原因。后者还引入了constexpr 规则所隐含的inline(在本问答中讨论的上下文中),可以推测它已被添加以避免诸如static inline constexpr const ... = ... 之类的构造(如前者所使用的)。最后,N4147 也应该很有趣。
    【解决方案2】:

    然而 S::i 的定义似乎在 C++17 中有内部链接 模式好像 constexpr 表示内联。它是否正确?如果是这样 标准中的证明?

    是的,没错。 cppreference

    内联说明符,当用于变量的 decl-specifier-seq 时 具有静态存储持续时间(静态类成员或命名空间范围 variable),声明该变量为内联变量。

    声明为 constexpr 的静态成员变量(但不是命名空间范围变量)隐含地是内联变量。 (C++17 起)

    【讨论】:

    • 最后(粗体)句子反映了 10.1.5 p1 s2。据我了解“声明的 constexpr”的措辞,静态数据成员必须在类定义中声明为 constexpr ,但这里不是这种情况。这就是为什么我写了我认为这句话不适用于这里。换句话说:S.cpp 中的 constexpr 定义是否意味着 S::i 被声明为 constexpr,尽管它在类定义中没有声明为 constexpr?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-15
    相关资源
    最近更新 更多