c++ 与要定义的类类型相同的静态constexpr成员

x7rlezfr  于 2023-03-20  发布在  其他
关注(0)|答案(5)|浏览(129)

我想让一个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的初始化器。
尝试三:

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

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

fae0ux8s

fae0ux8s1#

如果我对《标准》的理解正确的话,这是不可能的。
(§9.4.2/3)[......]文本类型的静态数据成员可以在类定义中用constexpr说明符声明;如果是这样,它的声明应该指定一个花括号或等号初始化式,其中每个赋值表达式的初始化子句都是常量表达式。
从上面(沿着在静态数据成员声明中没有关于非文本类型的单独声明这一事实),我认为可以得出结论:constexpr的静态数据成员必须是文本类型(定义见第3.9/10节),它必须将其定义包含在声明中。使用以下代码可以满足后一个条件:

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

这与您的尝试1类似,但没有类外部定义。
然而,由于Foo在声明/定义静态成员时是不完整的,编译器无法检查它是否是一个文本类型(如3.9节所定义的),因此它拒绝了代码。
注意,this post-C++-11 document (N3308)讨论了标准中constexpr当前定义的各种问题,并提出了修改建议。特别是,“建议的措辞”部分建议修改§3.9/10,暗示将不完整类型作为一种文字类型包括在内。如果该修改被接受到标准的未来版本中,你的问题就解决了。

aiazj4mn

aiazj4mn2#

我认为GCC拒绝您的尝试3是不正确的。在C++11标准(或其任何可接受的缺陷报告)中没有规则规定,如果先前的声明是constexpr,则变量的重新声明必须是constexpr。最接近该规则的标准是**[dcl.constexpr](7.1.5)/1_**:
如果函数或函数模板的任何声明具有constexpr规范,则其所有声明都应包含constexpr规范。
Clang的constexpr实现接受您的尝试3。

vdzxcuhz

vdzxcuhz3#

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

叮当声错误:

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

wz3gfoph4#

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

英尺高

#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"

主.cpp

#include "foo.h"

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

yruzcnhs5#

如果你像我一样,试图创建类似enum的类,我所想到的最好的方法是使用CRTP将行为放在基类中,然后派生类是“真实的的”类,它的存在只是为了将类似“枚举器”的值作为static constexpr inline Base成员。但是它的行为类似于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一样。

相关问题