【问题标题】:constexpr reference to a variable of an anonymous structconstexpr 对匿名结构变量的引用
【发布时间】:2016-07-23 11:01:47
【问题描述】:

给定struct B,它从struct A 继承了一个匿名的union 数据成员:

#include <cstddef>

struct A
{
    union
    {
        struct { int a, b, c; };
        int vals[3];
    };
};

struct B: A
{
    constexpr B(int x)
    :
        A{{{ x, x, x }}}
    {}

    constexpr int& operator[](size_t i)
    {
        return this->vals[i];
    }

    constexpr const int& operator[](size_t i) const
    {
        return this->vals[i];
    }
};

我声明了一个B 类型的constexpr 变量,然后调用它的operator[] 将返回值分配给constexpr int

int main()
{
    constexpr B b(7);
    constexpr int i = b[2];

    return 0;
}

但是 Clang 3.8 给了我错误信息

constexpr variable 'i' must be initialized by a constant expression

这个问题与匿名 union 有关,因为当我简单地使用 int vals[3] 时,一切正常。

我缺少 C++14 constexpr 限制吗?

【问题讨论】:

  • 肯定和工会有关。 gcc 5.3.1 在 "A{ x, x, x }" 初始化程序上发牢骚——错误:无法将'x'从'int'转换为'A::'
  • 有趣,Clang 没有抱怨初始化。 A 是否违反聚合初始化规则?
  • 好吧,在折腾之后,如果我给联合一个正式的字段名称:“union { ... } u;”,然后声明一个显式构造函数:“A(int aa,int bb , int cc) : u{aa, bb, cc}{}",gcc 5.3.1 接受该部分,但随后出现了关于 constexpr 运算符 []s 的重大故障。 “错误:constexpr 非静态成员函数‘int& B::operator[](size_t)’的封闭类不是文字类型”。我自己并不确定这整件事。无法将我的大脑完全包裹在 constexpr 运算符 [] 周围,这样做。
  • @SamVarshavchik 不知道为什么,但看起来 GCC 坚持使用 A({x, x, x}) 而不是 A{x,x,x} 或 A{{x,x, x}},所以我更新了我的答案。但是,是的,它对原始问题没有任何作用。
  • 好的,我对代码进行了测试,而 gcc 5.3.1 吞下了整件事。因此,通过适当的殴打和折磨,gcc 5.3.1 将接受这一点。编辑:只是声明,gcc 仍然有 main() 的问题。

标签: c++ c++11 c++14 unions c++17


【解决方案1】:

这是不允许的:

constexpr int i = b[2];

b[2] 不是常量表达式,因为它包含 (ref: N4140 [expr.const]/2.8)

左值到右值的转换 (4.1) 或修改 (5.17, 5.2.6, 5.3.2),应用于引用联合或其子对象的非活动成员的左值;

联合的活动成员是结构,因为您初始化了该成员。 int 数组处于非活动状态。

如果您将 operator[] 函数更改为 switch 并返回该结构的成员,它应该可以工作。

注意: 访问非活动成员会导致未定义的行为。虽然常见的编译器支持联合别名作为扩展,但如果您可以将代码设计为不使用联合别名,则会避免一些麻烦。


匿名结构及其初始化程序也存在问题。具体来说,[class.union]/5:

union { member-specification } ; 形式的联合称为匿名联合;它定义了一个未命名类型的未命名对象。匿名联合的成员规范应仅定义非静态数据成员。 [注意:嵌套类型、匿名联合和函数不能在匿名联合中声明。 ——尾注]

所以你不能在匿名联合中拥有匿名结构。您需要将其中之一设为非匿名。例如:

struct A
{
    struct AU { int a, b, c; };
    union
    {
        AU a;
        int vals[3];
    };
};

与初始化器: A({x, x, x})一起使用。您看到的围绕 A 初始化程序的不一致行为可能是 gcc 错误。

【讨论】:

  • 匿名联合中的匿名结构很常见,所有主要编译器都支持,所以这不是问题。我什至没有注意到引用的标准指出匿名联合中不允许使用未命名的结构。它谈到嵌套类型,但是异常结构不是类型声明。
  • @plasmacel 我认为它算作嵌套类型。 clang 3.8 发出警告,表明它是一个扩展。
  • clang 可能会警告您,因为它是一个编译器扩展,因为严格来说,我知道匿名结构还不是 C++ 标准的一部分。然而,它们是 C11 标准的一部分,这就是为什么所有主要的 C/C++ 编译器也从 C++11 或更早版本支持它的原因。
  • 同时我发现了初始化错误。 A 应该初始化为A{{{ x, x, x }}},因为我们不只是初始化A,因为它没有用户定义的构造函数。我们在聚合 union 内初始化聚合 struct 在聚合 A 内。
  • "联合的活动成员是结构,因为您初始化了该成员。int 数组是非活动的。" 通用初始序列规则不是这样吗没有实际意义?
【解决方案2】:

除了 M.M 的回答之外,根据 C++ union initialization 规则,聚合初始化器始终只初始化第一个联合成员,该成员成为该联合的活动成员。

因此将A 更改为int vals[3] union 中的第一个声明:

struct A
{
    union
    {
        int vals[3];
        struct { int a, b, c; };
    };
};

或定义一个初始化成员int vals[3]的构造函数,而不是初始化第一个union成员的聚合初始化:

struct A
{
    A(int a, int b, int c)
    : vals{ a, b c }
    {}

    union
    {
        struct { int a, b, c; };
        int vals[3];
    };
};

解决了在constexpr 表达式中读取匿名union 成员int vals[3] 的问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-09-03
    • 2021-08-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-04
    • 1970-01-01
    相关资源
    最近更新 更多