为啥这个 constexpr 静态成员函数在调用时不被视为 constexpr?

Posted

技术标签:

【中文标题】为啥这个 constexpr 静态成员函数在调用时不被视为 constexpr?【英文标题】:Why is this constexpr static member function not seen as constexpr when called?为什么这个 constexpr 静态成员函数在调用时不被视为 constexpr? 【发布时间】:2016-05-12 17:56:32 【问题描述】:

为什么这个 constexpr static 成员函数,由 //! Nah 注释标识,在调用时不被视为 constexpr

struct Item_id

    enum Enum
    
        size, position, attributes, window_rect, max_window_size, _
    ;

    static constexpr int n_items_ = _;                          // OK
    constexpr auto member_n_items() const -> int  return _;   // OK
    static constexpr auto static_n_items() -> int  return _;  // OK
    static constexpr int so_far = n_items_;                     // OK
    #ifndef OUT_OF_CLASS
        static constexpr int bah = static_n_items();            //! Nah.
    #endif
;

constexpr auto n_ids() -> int  return Item_id().member_n_items();     // OK

auto main() -> int

    #ifdef OUT_OF_CLASS
        static constexpr int bah = Item_id::static_n_items();   // OK
    #endif

MinGW g++ 5.1 报告

constexpr.cpp:12:46:错误:在常量表达式中调用了“静态 constexpr int Item_id::static_n_items()” static constexpr int bah = static_n_items(); //!不。

Visual C++ 2015 报告

constexpr.cpp(12):错误 C2131:表达式未计算为常量 constexpr.cpp(12):注意:失败是由调用未定义的函数或未声明的“constexpr”引起的 constexpr.cpp(12):注意:参见“Item_id::static_n_items”的用法

我的文本编辑器坚持调用中的名称与函数定义中的名称相同。

这似乎与不完整的类有关,因为定义了OUT_OF_CLASS,它编译得很好。

但是为什么n_items_ 数据有效,为什么这样的规则(对我来说没有意义)?

【问题讨论】:

gcc6.1和clang3.8也不喜欢。 请注意,将bah 的声明和初始化移出struct 并执行static constexpr int bah = Item_id::static_n_items(); 会在G++ 5.3 上编译 @GillBates:谢谢,我发现了同样的问题(见修订后的问题)。这很奇怪。 wg21.link/cwg1626 【参考方案1】:

从内存中,成员函数体仅在类被完全定义后才被评估。

static constexpr int bah = static_n_items(); 

构成类定义的一部分,但它指的是一个(静态)成员函数,它还不能被定义。

解决办法:

将常量表达式推迟到基类并从中派生。

例如:

struct Item_id_base

    enum Enum
    
        size, position, attributes, window_rect, max_window_size, _
    ;

    static constexpr int n_items_ = _;                          // OK
    constexpr auto member_n_items() const -> int  return _;   // OK
    static constexpr auto static_n_items() -> int  return _;  // OK
    static constexpr int so_far = n_items_;                     // OK
;

struct Item_id : Item_id_base

    #ifndef OUT_OF_CLASS
        static constexpr int bah = static_n_items();            // now OK
    #endif
;

constexpr auto n_ids() -> int  return Item_id().member_n_items();     // OK

auto main() -> int

    #ifdef OUT_OF_CLASS
        static constexpr int bah = Item_id::static_n_items();   // OK
    #endif

您认为标准为什么不允许这样做?

因为这是非法的:

struct Item_id
   
    // ... etc.

    #ifndef OUT_OF_CLASS
        static constexpr int bah;// = static_n_items();            //! Nah.
    #endif
;

constexpr int Item_id::bah = static_n_items();

而且一个 constexpr 必须有一个 constexpr 定义。我们唯一可以定义它的地方是在它的声明期间......

...所以通过推导它不能引用任何尚未定义主体的函数。

我不知道在哪里查看所有这些标准。可能有 5 个不同的,看似无关的子句:)

【讨论】:

我认为你可能是对的,谢谢!但我一直认为重写(在类之后使用函数定义)只是一个概念工具。我不记得在标准中看到它。那么,您知道标准的哪一部分对此做了规定吗? @Cheersandhth.-Alf 半心半意的回应添加到答案中。在漫长的一天结束时,我没有胃口去拖拉所有相关条款的标准……我相信你知道这种感觉! 没有要求在其第一个声明中声明变量constexpr【参考方案2】:

[class.mem]/2

在类成员规范中,类在函数体、默认参数、异常规范和默认成员初始化器(包括嵌套类)。否则它在自己的类member-specification中被认为是不完整的。

在类的static 数据成员的初始化程序中,该类不完整。初始化器只能看到它之前的成员声明,​​它可以看到的任何成员函数都被认为是已声明但未定义。对已声明但未定义的函数的调用不能是常量表达式。

【讨论】:

感谢您的挖掘! [basic.scope.class]/1 用稍微不同的语言说了几乎相同的东西,但我还没有找到支持我的主张的参考资料,即“它可以看到的任何成员函数都被视为已声明但未定义”。

以上是关于为啥这个 constexpr 静态成员函数在调用时不被视为 constexpr?的主要内容,如果未能解决你的问题,请参考以下文章

为啥**不**将函数声明为`constexpr`?

c#里为啥有的使用时函数需要new一个对象而有的不用?为啥不直接调用就好?

为啥调用静态成员函数。或 -> 语法合法? [复制]

使用 constexpr 成员函数初始化 constexpr 成员变量

C++ 编译器优化 - 为啥需要 constexpr?

一个类不能有自己的静态 constexpr 成员实例吗?