常量表达式中的静态成员访问

Posted

技术标签:

【中文标题】常量表达式中的静态成员访问【英文标题】:Static member access in constant expressions 【发布时间】:2022-01-01 10:49:23 【问题描述】:

可以通过两种方式访问​​静态类成员函数或变量:通过对象(obj.member_fun()obj.member_var)或通过类(Class::member_fun()Class::member_var)。但是,在constexpr 函数中,Clang 给出了对象访问错误并要求使用类访问:

struct S 

    constexpr static auto s_v = 42;    
    constexpr static auto v()  return s_v; 
;

#define TEST 1

constexpr auto foo(S const& s [[maybe_unused]]) 

#if TEST
    constexpr auto v = s.v();   // ERROR for clang, OK for gcc
#else    
    constexpr auto v = S::v();  // OK for clang and gcc
#endif
    return v;


constexpr auto bar(S const& s [[maybe_unused]])

#if TEST   
    constexpr auto v = s.s_v;   // ERROR for clang, OK for gcc
#else    
    constexpr auto v = S::s_v;  // OK for clang and gcc
#endif    
    return v;


int main() 

Live Example 使用 -std=c++1z#define TEST 1 为 Clang 5.0 SVN 编译,并带有错误消息:

Start
prog.cc:12:24: error: constexpr variable 'v' must be initialized by a constant expression
    constexpr auto v = s.v();   // ERROR for clang, OK for gcc
                       ^~~~~
prog.cc:22:24: error: constexpr variable 'v' must be initialized by a constant expression
    constexpr auto v = s.s_v;   // ERROR for clang, OK for gcc
                       ^~~~~
2 errors generated.
1
Finish

问题:这是一个 Clang 错误,还是 gcc 在接受 constexpr 函数中静态成员访问的两种语法形式时过于宽松?

【问题讨论】:

gcc 编译出来的代码是否工作 @VIPER 是的(虽然这是一个简化的例子) 在实践中,即使允许,你也不应该这样做...... 好像和this question类似。 foobarconstexpr 函数无关紧要。见example。 【参考方案1】:

Clang 似乎是正确的。使用成员访问语法[class.static/1] 访问静态成员时:

类 X 的静态成员 s 可以使用qualified-id来引用 表达式 X​::​s;不需要使用类成员访问 引用静态成员的语法。 可以引用静态成员 使用类成员访问语法,在这种情况下对象 表达式被计算。

所以s.v() 将导致s 被评估。现在,根据[expr.const/2.11],s 不是常量表达式:

2 表达式 e 是核心常量表达式,除非求值 的 e,遵循抽象机的规则,将评估一个 以下表达式:

[...]

引用变量或数据成员的 id 表达式 类型,除非引用具有前面的初始化并且: (2.11.1) - 使用常量表达式初始化或 (2.11.2) - 它的生命周期始于对 e 的评估;

s 没有使用常量表达式进行前面的初始化,不在foo 的范围内。


如果你想基于函数参数访问静态成员,而不是硬编码类型,前进的方向是std::remove_reference_t<decltype(s)>。这是 Clang 和 GCC 都接受的:

#include <type_traits>

struct S 

    constexpr static auto s_v = 42;    
    constexpr static auto v()  return s_v; 
;

constexpr auto foo(S const& s) 

    constexpr auto v = std::remove_reference_t<decltype(s)>::v();
    return v;


constexpr auto bar(S const& s)

    constexpr auto v = std::remove_reference_t<decltype(s)>::s_v;
    return v;


int main() 

【讨论】:

我还记得听到过类似“参数不是constexpr,但您可以通过将值包装在 lambda 中来解决这个问题”。 (虽然我现在不能让它工作:godbolt.org/g/V8tzMG) @JornVernee - 我看不到 lambda 在这里有什么帮助。也许你可以摆弄你的例子来演示? 我认为这个想法是constexpr lambda 可以返回一个constexpr 值,然后可以使用它来初始化一个constexpr 变量。我可以使用该 lambda 在 main 中初始化 s,但由于某种原因,在传递给函数时初始化 r 时它不起作用。 如果 GCC 在这里出错,如果有人向他们提交错误,那就太好了。 @ClaasBontus 这个技巧也适用于原始版本,您可以从v 中删除constexpr 以使其编译。我猜constexpr 只查看初始化表达式,而不查看该表达式的结果值。【参考方案2】:
constexpr auto v = s.v();   // ERROR for clang, OK for gcc

我想这取决于你是在 C++11 还是 C++14 模式下编译。如果你看cppreference,你会发现(重点是我加的):

核心常量表达式是不具有以下任何一项的任何表达式 (...) 6) this 指针,除非用于非静态成员函数内的类成员访问(C++14 前) 6) this 指针,在 constexpr 函数中 或被 作为表达式的一部分计算的 constexpr 构造函数中除外(C++14 起)

因此,在 C++11 中,s.v() 内部发生的任何事情都不会被视为常量表达式,因为它使用 this 指针,但它不是非静态成员访问类成员的函数(它是static)。

然而,根据 C++14,它会是,因为它正在评估 constexpr 函数作为表达式的一部分,因此“没有任何”规则集上的“除外 if”子句捕获.

现在不要问我这是否有意义,或者是否有人应该理解这一点...... :-)

【讨论】:

用 c++1z 标签更新了问题

以上是关于常量表达式中的静态成员访问的主要内容,如果未能解决你的问题,请参考以下文章

静态(数据)成员与类

静态字段静态函数成员常量

PHP常见概念混淆之PHP类常量静态属性和属性的区别

火山中文编程(PC) -- 变量与常量

静态修饰符static,类中的常量定义修饰符

访问成员表达式的值