常量表达式中的静态成员访问
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() {}
访问静态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) Thethis
pointer, except if used for class member access inside a non-static member function (until C++14)
6) Thethis
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() {}