与定义的类具有相同类型的静态 constexpr 成员

问题描述 投票:0回答:5

我希望 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
5个回答
38
投票

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

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

从上面(以及静态数据成员声明中没有关于非文字类型的单独声明这一事实),我相信,

constexpr
的静态数据成员必须是文字类型(如§3.9/10 中定义),并且它必须将其定义包含在声明中。后一个条件可以通过使用以下代码来满足:

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

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

但是,由于

Foo
在声明/定义静态成员时不完整,编译器无法检查它是否是文字类型(如第 3.9/10 节中定义),因此会拒绝该代码。

请注意,有 C++-11 后文档 (N3308),其中讨论了标准中

constexpr
当前定义的各种问题,并提出了修改建议。具体来说,“建议的措辞”部分建议对 §3.9/10 进行修订,暗示将不完整类型包含为一种文字类型。如果该修正案被接受到标准的未来版本中,您的问题就会得到解决。


19
投票

我认为 GCC 拒绝您的尝试 3 是不正确的。C++11 标准(或其任何接受的缺陷报告)中没有规则规定变量的重新声明必须是

constexpr
前提是先前的声明是。最接近该规则的标准位于 [dcl.constexpr](7.1.5)/1_:

如果函数或函数模板的任何声明具有

constexpr
说明符,则其所有声明都应包含
constexpr
说明符。

Clang 的

constexpr
实现接受您的尝试 3。


14
投票

Richard Smith 的答案的更新,尝试 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

铿锵错误:

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

2
投票

早些时候我也遇到了同样的问题,并遇到了这个十年前的问题。 我很高兴地报告,在接下来的几年里,出现了一个解决方案;我们只需要做类似上面“尝试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"

main.cpp

#include "foo.h"

int main(int, char **) { return 0; }

0
投票

如果像我一样,您正在尝试创建类似于

enum
的类,那么我想到的最好方法是使用 CRTP 将行为放入基类中,然后派生类就是存在的“真实”类只是为了将类似“枚举器”的值作为
static constexpr inline Base
成员。这意味着
Foo::yes
不是
Foo
类型,但它的行为类似于
Foo
并且可以隐式转换为
Foo
,因此看起来非常接近。 https://godbolt.org/z/rTEdKxE3h

template <class Derived>
class StrongBoolBase {
public:
    explicit constexpr StrongBoolBase() noexcept : m_val{false} {}
    explicit constexpr StrongBoolBase(bool val) noexcept : m_val{val} {}

    [[nodiscard]] constexpr explicit operator bool() const noexcept { return m_val; }

    [[nodiscard]] constexpr auto operator<=>(const StrongBoolBase&) const noexcept = default;
    [[nodiscard]] constexpr auto operator not() const noexcept {
        return StrongBoolBase{not this->asBool()};
    }
    [[nodiscard]] constexpr bool asBool() const noexcept {
        return m_val;
    }

private:
  bool m_val;
};

template <class Tag>
class StrongBool : public StrongBoolBase<StrongBool<Tag>> {
    using Base = StrongBoolBase<StrongBool<Tag>>;
public:
    //////// This is the interesting part: yes and no aren't StrongBool:
    inline static constexpr auto yes = Base{true};
    inline static constexpr auto no = Base{false};
    using Base::Base;
    constexpr StrongBool(Base b) noexcept : Base{b} {}    
};

唯一的问题是,如果您开始使用

decltype(Foo::yes)
,就好像它是
Foo

© www.soinside.com 2019 - 2024. All rights reserved.