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

Static member access in constant expressions

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

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 函数中静态成员访问的两种语法形式时过于宽松?

constexpr auto v = s.v();   // ERROR for clang, OK for gcc

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

A core constant expression is any expression that does not have any one of the following
(...)
6) The this pointer, except if used for class member access inside a non-static member function (until C++14)
6) The this pointer, except in a constexpr function or a constexpr constructor that is being evaluated as part of the expression (since C++14)

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

然而,根据 C++14,它会是,因为它正在评估一个 constexpr 函数作为表达式的一部分,所以 "does not have any of" 集上的 "except if" 子句规则捕获量。

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

铿锵似乎是对的。使用成员访问语法 [class.static/1]:

访问静态成员时

A static member s of class X may be referred to using the qualified-id expression X​::​s; it is not necessary to use the class member access syntax to refer to a static member. A static member may be referred to using the class member access syntax, in which case the object expression is evaluated.

因此 s.v() 将导致 s 被计算。现在,根据 [expr.const/2.11]s 不是常量表达式:

2 An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine, would evaluate one of the following expressions:

[...]

an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and either:
(2.11.1) - it is initialized with a constant expression or
(2.11.2) - its lifetime began within the evaluation of 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() {}