【问题标题】:`static constexpr auto` data-member initialized with unnamed enum`static constexpr auto` 使用未命名枚举初始化的数据成员
【发布时间】:2014-06-03 15:20:28
【问题描述】:

我正在使用 clang++-3.4 进行 C++11 项目,并决定使用 g++-4.8.2 进行编译,以防产生的错误有任何差异。原来g++拒绝了clang++接受的一些代码。我已将问题简化为下面给出的 MWE。


enum { a };

template <class T>
struct foo
{
    static constexpr auto value = a;
};

int main()
{
    static constexpr auto r = foo<int>::value;
}

foo.cpp:5:23: 错误:'const&lt;anonymous enum&gt; foo&lt;int&gt;::value',使用匿名类型声明,已使用但从未定义 [-fpermissive]

static const auto value = A;

我需要一些帮助来回答以下两个问题:

  • 哪个编译器对标准的解释是正确的?我假设一个编译器在接受或拒绝代码方面是正确的,而另一个是错误的。

  • 我该如何解决这个问题?我无法命名匿名枚举,因为它来自第三方库(在我的例子中,枚举是 Eigen::RowMajorEigen::ColMajor)。

【问题讨论】:

  • 你定义了变量吗?或者错误信息是什么?
  • @Arcoth 我认为他没有提供定义,如果你这样做,错误就会消失。 Here's 错误消息。我认为问题是提及foo&lt;T&gt;::value 是否构成odr 使用。 gcc 似乎认为是,而 clang 认为不是。
  • @Praetorian 是的,抱歉,我愚蠢地忘记包含实际的错误消息。

标签: c++ c++11 enums language-lawyer auto


【解决方案1】:

谁的责任?

GCC 错误地拒绝了您的 sn-p,根据 C++11 标准 (N3337),它是合法的。带有证明和解释的引文位于本文末尾。

解决方法 (A) - 添加缺少的定义

template <class T>
struct foo {
    static constexpr auto value = a;
    typedef decltype(a) value_type;
};

template<class T>
constexpr typename foo<T>::value_type foo<T>::value;


解决方法 (B) - 使用枚举的底层类型作为占位符

#include <type_traits>

template <class T>
struct foo {
  static const std::underlying_type<decltype(a)>::type value = a;
};

标准是怎么说的? (N3337)

如上所述,sn-p 是合法的C++11,可以在以下引用的部分中阅读。


我们什么时候可以使用没有链接的类型?

[basic.link]p8 有详细的措辞来描述一个类型何时是“无链接”,并声明未命名的枚举算作这种类型。

[basic.link]p8 还明确指出了不能使用这种类型的三个上下文,但没有一个上下文适用于我们的使用,因此我们是安全的。

没有链接的类型不应用作具有外部链接的变量或函数的类型,除非

  • 实体具有 C 语言链接 (7.5),或
  • 实体在未命名的命名空间 (7.3.1) 中声明,或
  • 实体不是odr-used (3.2) 或在同一个翻译单元中定义


你确定我们可以在这种情况下使用auto吗?

是的,这可以通过以下引用来证明:

7.1.6.4p auto 说明符 [dcl.spec.auto]

autotype-specifier 也可用于在选择语句 (6.4) 或迭代语句 (6.5) 的条件下声明变量,在 type- new-expression (5.3.4) 的 new-type-idtype-id 中的 specifier-seq,在 for-range-declaration 中,以及使用出现在 member-specification 中的 brace-or-equal-initializer 声明静态数据成员em> 的类定义 (9.4.2)。

【讨论】:

  • 注意template&lt;class T&gt; struct foo { static constexpr auto value = a; } template&lt;class T&gt; constexpr decltype (foo&lt;T&gt;::value) foo&lt;T&gt;::value; 应该是一种解决方法,但最新的 clang 拒绝了它。目前正在调查它是否是一个错误。
  • 感谢您提供全面的答案和解决方法。您是否为 GCC 中的原始错误提交了报告?如果你愿意,你可以继续做。否则,我可以做的荣誉。
  • @void-pointer 老实说,我正要这样做,你想写一个吗?如果是这样的话;都是你的。
  • 如果你愿意,你可以这样做;这完全取决于你。我只是提供以防万一你宁愿别人这样做。
  • @FilipRoséen-refp 我不确定[basic.link]p8 在这里是否真的相关。它说“没有链接的类型不应用作变量或函数的类型...”,其中在标准中提到枚举数名称是变量?
【解决方案2】:

哪个编译器对标准的解释是正确的?

gcc 不正确。 §9.4.2/3:

可以在类中声明文字类型的静态数据成员 使用 constexpr 说明符定义;如果是,其声明应 指定一个大括号或相等初始化器,其中每个初始化器子句 也就是说,赋值表达式是一个常量表达式。 会员 如果它是 odr-used (3.2) 中的,仍应在命名空间范围内定义 程序和命名空间范围定义不应包含 初始化器。

并且根据第 3.2 节,该名称没有被 ODR 使用:

名称显示为潜在求值表达式的变量是 odr-used 除非它是一个满足出现在常量表达式(5.19)和左值到右值的要求的对象 立即应用转换 (4.1)。

确实如此:它确实满足出现在常量表达式中的要求,并且立即应用左值到右值的转换(它用作对象的初始化器)。所以GCC的拒绝是不正确的。


一种可能的解决方法是定义成员(但没有占位符类型)。这个定义对于 Clang 和 GCC 来说都足够了:

template< typename T >
constexpr decltype(a) foo<T>::value;

【讨论】:

    【解决方案3】:

    decltype 的解决方法:

    enum { a };
    
    template <class T>
    struct foo
    {
        static constexpr auto value = a;
    };
    
    template <class T>
    constexpr decltype(a) foo<T>::value;
    
    int main()
    {
        static constexpr auto r = foo<int>::value;
    }
    

    【讨论】:

      猜你喜欢
      • 2020-09-20
      • 1970-01-01
      • 1970-01-01
      • 2014-06-20
      • 1970-01-01
      • 2016-10-15
      • 1970-01-01
      • 2021-04-18
      • 2014-09-08
      相关资源
      最近更新 更多