【问题标题】:static constexpr member of same type as class being defined与正在定义的类相同类型的静态 constexpr 成员
【发布时间】:2012-08-13 04:08:00
【问题描述】:

我希望 C 类有一个 C 类型的静态 constexpr 成员。这在 C++11 中可能吗?

尝试 1:

struct Foo {
    constexpr Foo() {}
    static constexpr Foo f = Foo();
};
constexpr Foo Foo::f;

g++ 4.7.0 说:“不完整类型的无效使用”指的是 Foo() 调用。

尝试 2:

struct Foo {
    constexpr Foo() {}
    static constexpr Foo f;
};
constexpr Foo Foo::f = Foo();

现在的问题是在类定义中缺少 constexpr 成员 f 的初始化程序。

尝试 3:

struct Foo {
    constexpr Foo() {}
    static const Foo f;
};
constexpr Foo Foo::f = Foo();

现在 g++ 抱怨 Foo::f 的重新声明与 constexpr 不同。

【问题讨论】:

    标签: c++ c++11 constexpr


    【解决方案1】:

    如果我正确解释标准,这是不可能的。

    (§9.4.2/3) [...] 文字类型的静态数据成员可以在 带有 constexpr 说明符的类定义;如果是这样,它的声明应指定一个大括号或等式初始化器,其中作为赋值表达式的每个初始化器子句都是一个常量表达式。 [...]

    从上面(以及在静态数据成员声明中没有关于非文字类型的单独声明的事实),我相信constexpr 的静态数据成员必须是文字type(定义见 §3.9/10),并且它的定义必须包含在声明中。使用以下代码可以满足后一个条件:

    struct Foo {
      constexpr Foo() {}
      static constexpr Foo f {};
    };
    

    类似于您的尝试 1,但没有类外部定义。

    但是,由于 Foo 在静态成员的声明/定义时不完整,编译器无法检查它是否是文字类型(如 §3.9/10 中所定义),因此它拒绝代码.

    注意有this post-C++-11 document (N3308)讨论了标准中constexpr当前定义的各种问题,并提出了修改建议。具体来说,“提议的措辞”部分建议对 §3.9/10 进行修订,暗示将不完整类型作为一种文字类型包含在内。如果该修订被接受到标准的未来版本中,您的问题将得到解决。

    【讨论】:

    • +1 让我们在提供答案的同时了解当前的讨论状态!
    • 我刚刚遇到了这个确切的问题。这似乎与 static const 处理不完整类型的方式不一致。我想我现在只需要使用static const
    • 这不是完全错误的,但不如理查德史密斯的答案有用。声明为 const,然后在下面定义为 constexpr。和 - 请注意 - 这现在适用于内联定义以及 constexpr
    • C++20 改变了吗?您知道修改建议的状态吗?
    【解决方案2】:

    我认为 GCC 拒绝您的尝试 3 是不正确的。C++11 标准(或其任何已接受的缺陷报告)中没有规定重新声明变量必须是 constexpr iff声明是。最接近该规则的标准是 [dcl.constexpr](7.1.5)/1_

    如果函数或函数模板的任何声明具有constexpr 说明符,则其所有声明都应包含constexpr 说明符。

    Clang 的 constexpr 实现接受您的尝试 3。

    【讨论】:

    • 也许,但在尝试 3 中,变量只有 const 而不是 constexpr,不是吗?
    • 在尝试 3 中,变量为 constexpr,因为在定义中指定了 constexpr。标准的相关部分是 5.19/2:“左值到右值的转换 [...可能应用于...] 引用非易失性对象 的文字类型的非易失性左值用constexpr 定义
    • 您确定允许这样做吗?静态成员变量不能在类外引入,首先必须在类内以相同的类型声明(包括cv-qualifiers)。但是,我似乎找不到要求这样做的规则,以便查看 constexpr 是否从签名匹配中排除。
    • 好的,部分规则在 8.3p1 中:“当 declarator-id 合格时,声明应引用先前声明的类或命名空间成员限定符所指的“我仍然找不到指定类型和限定符必须匹配的位。
    • 您正在寻找 3.5p10:“在所有类型调整之后(在此期间 typedefs (7.1.3) 被它们的定义替换),所有引用给定变量的声明指定的类型或函数应该是相同的,除了数组对象的声明可以指定数组类型,这些数组类型因存在或不存在主要数组绑定而不同。请注意,constexprconst 添加到类型中,但它本身不是类型的一部分。
    【解决方案3】:

    Richard Smith's answer 的更新,尝试 3 现在可在 GCC 4.9 和 5.1 以及 clang 3.4 上编译。

    struct Foo {
      std::size_t v;
      constexpr Foo() : v(){}
      static const Foo f;
    };
    
    constexpr const Foo Foo::f = Foo();
    
    std::array<int, Foo::f.v> a;
    

    但是,当 Foo 是类模板时,clang 3.4 会失败,但 GCC 4.9 和 5.1 仍然可以正常工作:

    template < class T >
    struct Foo {
      T v;
      constexpr Foo() : v(){}
      static const Foo f;
    };
    
    template < class T >
    constexpr const Foo<T> Foo<T>::f = Foo();
    
    std::array<int, Foo<std::size_t>::f.v> a; // gcc ok, clang complains
    

    Clang 错误:

    error: non-type template argument is not a constant expression
    std::array<int, Foo<std::size_t>::f.v> a;
                    ^~~~~~~~~~~~~~~~~~~~~
    

    【讨论】:

    • 不幸的是,如果将定义放在 .h 文件中,最终会在链接时出现“多重定义”错误。而且如果你把它放在你的C++文件中,其他C++文件不知道它是constexpr。
    • @MartinC.Martin 这就是使用模板的原因,或者如果使用 C++17 你可以使用inline 变量。不过,Clang 和 MSVC 目前在使用模板作为编译时常量表达式时感到窒息。使用内联变量,这适用于 GCC、Clang 和 MSVC(当然,它们的相关版本)。
    • @monkey0506 我遇到了类似的问题。你能展示如何使用 C++17 内联变量来做到这一点吗?
    【解决方案4】:

    早些时候我遇到了同样的问题,遇到了这个十年前的问题。我很高兴地报告说,在这几年中出现了解决方案。我们只需要像上面的“尝试 3”那样做一些事情,但是将Foo::f 的定义标记为inline。使用g++ --std=c++17 编译的最小示例:

    foo.hpp

    #ifndef __FOO_HPP
    #define __FOO_HPP
    
    struct Foo
    {
        constexpr Foo() {}
        static const Foo f;
    };
    
    inline constexpr Foo Foo::f = Foo();
    
    #endif
    

    foo.cpp

    #include "foo.h"
    

    ma​​in.cpp

    #include "foo.h"
    
    int main(int, char **) { return 0; }
    

    【讨论】:

    • 这似乎不便携。它适用于 GCC 和 Clang,但 MSVC 失败并出现 error LNK2005: "public: static class ... already defined in ... docs.microsoft.com/en-us/cpp/error-messages/tool-errors/…
    • @aij 我刚刚在 Visual Studio 上尝试过这个,它对我来说很好。与 gcc/clang 一样,您需要将 /std:c++17 传递给 MSVC,这是我通过(在解决方案资源管理器中右键单击项目)-> 属性 -> 配置属性 -> C/C++ -> 语言 -> C++ 语言标准跨度>
    最近更新 更多