常量表达式中的静态成员访问
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类似。foo
和 bar
是 constexpr
函数无关紧要。见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 标签更新了问题以上是关于常量表达式中的静态成员访问的主要内容,如果未能解决你的问题,请参考以下文章