我希望 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
不同。
如果我正确解释该标准,这是不可能的。
(§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 进行修订,暗示将不完整类型包含为一种文字类型。如果该修正案被接受到标准的未来版本中,您的问题就会得到解决。
我认为 GCC 拒绝您的尝试 3 是不正确的。C++11 标准(或其任何接受的缺陷报告)中没有规则规定变量的重新声明必须是
constexpr
前提是先前的声明是。最接近该规则的标准位于 [dcl.constexpr](7.1.5)/1_:
如果函数或函数模板的任何声明具有
说明符,则其所有声明都应包含constexpr
说明符。constexpr
Clang 的
constexpr
实现接受您的尝试 3。
对 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;
^~~~~~~~~~~~~~~~~~~~~
早些时候我也遇到了同样的问题,并遇到了这个十年前的问题。 我很高兴地报告,在接下来的几年里,出现了一个解决方案;我们只需要做类似上面“尝试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; }
如果像我一样,您正在尝试创建类似于
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
。