__PRETTY_FUNCTION__ 在常量表达式中
__PRETTY_FUNCTION__ in constant expression
请参考这个片段:
#include <type_traits>
#include <string_view>
constexpr std::size_t strlen(char const* s) {
std::size_t n = 0;
while (*s++ != '[=11=]')
++n;
return n;
}
template <std::size_t>
struct X {};
int main() {
constexpr auto pf = __PRETTY_FUNCTION__; // gcc ok; clang ok; (1)
static_assert(std::string_view(__PRETTY_FUNCTION__) == std::string_view("int main()")); // gcc ok; clang ok; (2)
X<strlen(__PRETTY_FUNCTION__)> x; // gcc not ok; clang ok; (3)
}
Clang 8 会编译它,但 GCC 8.3 不会。参见 godbolt。 GCC 在行 (3)
上失败,尽管行 (1)
和 (2)
没问题。
如果我能够在行 (1)
中声明 pf
并在 static_assert
中使用 __PRETTY_FUNCTION__
则意味着表达式 __PRETTY_FUNCTION__
是核心常量表达式。如果我不能声明 X<strlen(__PRETTY_FUNCTION__)> x
这意味着 strlen(__PRETTY_FUNCTION__)
不是一个完整的常量表达式。
那么为什么strlen(__PRETTY_FUNCTION__)
不是整型常量表达式呢?是标准暗示的,还是 GCC 的错误?
__PRETTY_FUNCTION__
不标准。因此,编译器可以在不同的地方(解析时、构建 AST 时或链接时)实现它。
如果它应该在解析时实现,那么它可以是一个常量表达式(我猜这就是clang所做的)。
但是,如果它是在链接时实现的(即,编译器为它发出一个符号,链接器将解析它),它就不能是常量表达式。
我认为 GCC 使用后一种情况。
请注意,在这种情况下,您可以采用其中的 sizeof(),因为如果您需要编译时常量字符串的长度计算,它是 const char[]
。
因此,将表达式 3 替换为:
X<sizeof(__PRETTY_FUNCTION__) - 1> x;
它在两个编译器上都能正常编译。
编辑:正如 NathanOliver 指出的那样,GCC 似乎将 __PRETTY_FUNCTION__
的签名视为 static const char[]
,而 clang/visual 工作室将其视为 static constexpr const char[]
。这在 GCC 中是一个令人痛苦的麻烦(不是错误,因为它不是标准的)并且他们似乎已经在 >8.0.0 版本中修复了它。
在表达式(1)和表达式(2)中,__PRETTY_FUNCTION__
被衰减为const char*
(指针是常量,但数据什么也说不出来)。对我来说,表达式 (2) 可能无法证明任何事情,因为不能保证等式两边的指针应该相等,即使它们指向 "same" 内容。 string_view
构造函数期望 const char*
,因此除了 __PRETTY_FUNCTION__
之外的任何可能衰减到 const char*
的东西都会传递表达式 (2).
我仍然担心 (2)
行,所以我想出了一些不适合发表评论的内容。
我稍微修改了一下代码片段,请参考this:
#include <type_traits>
#include <string_view>
constexpr std::size_t strlen(char const* s) {
std::size_t n = 0;
while (*s++ != '[=10=]')
++n;
return n;
}
template <std::size_t>
struct X {};
static char const PF[] = "int main()";
int main() {
constexpr auto pf = std::string_view(__PRETTY_FUNCTION__); // gcc ok; clang ok; (1)
static_assert(pf == std::string_view("int main()")); // gcc ok; clang ok; (2)
X<strlen(__PRETTY_FUNCTION__)> x; // gcc not ok; clang ok; (3)
static_assert(pf[0] == 'i'); // not ok; (4)
X<pf.size()> x1; // ok; (5)
X<strlen(pf.data())> x2; // not ok; (6)
static_assert(__builtin_constant_p(pf.data()) == 0); // ok (7)
static_assert(__builtin_strlen(pf.data()) == 10); // ok (8)
static_assert(__builtin_memcmp(pf.data(), "int main()", __builtin_strlen(pf.data())) == 0); // ok (9)
static_assert(std::char_traits<char>::compare(pf.data(), "int main()", std::char_traits<char>::length(pf.data())) == 0); // ok (10)
static_assert(std::char_traits<char>::length(PF) == 10); // not ok (11)
static_assert(__builtin_strlen(PF) == 10); // not ok (12)
}
据我所知,static_assert
将无法证明 (2)
的值,如果它不是 believe
表达式是 constexpr(如行 (4)
).但是尽管 (2)
和 (4)
行有问题,(5)
行对 GCC 来说似乎没问题。所以,我偷看了std::char_traits<char>
。这些特征使用 __builtin_constant_p
在 __builtin_*
实现和像我的 strlen
这样的实现之间进行调度。 (7)
行声明 "the constexprness of the __PRETTY_FUNCTION__
can't be proven"(请参阅 gcc doc),但尽管如此,表达式 __builtin_memcmp(__PRETTY_FUNCTION__)
仍可在编译时求值(请参阅 (8)
行)。
在 (11)
和 (12)
中考虑失败,可以得出结论,在某些情况下 __PRETTY_FUNCTION__
的行为就好像它被声明为 static constexpr char const []
而在其他情况下作为 static char const []
。换句话说,如果 __PRETTY_FUNCTION__
有一个类型,那么它在编译的所有步骤中都不一致(我指的是 GCC 8.3)。
请参考这个片段:
#include <type_traits>
#include <string_view>
constexpr std::size_t strlen(char const* s) {
std::size_t n = 0;
while (*s++ != '[=11=]')
++n;
return n;
}
template <std::size_t>
struct X {};
int main() {
constexpr auto pf = __PRETTY_FUNCTION__; // gcc ok; clang ok; (1)
static_assert(std::string_view(__PRETTY_FUNCTION__) == std::string_view("int main()")); // gcc ok; clang ok; (2)
X<strlen(__PRETTY_FUNCTION__)> x; // gcc not ok; clang ok; (3)
}
Clang 8 会编译它,但 GCC 8.3 不会。参见 godbolt。 GCC 在行 (3)
上失败,尽管行 (1)
和 (2)
没问题。
如果我能够在行 (1)
中声明 pf
并在 static_assert
中使用 __PRETTY_FUNCTION__
则意味着表达式 __PRETTY_FUNCTION__
是核心常量表达式。如果我不能声明 X<strlen(__PRETTY_FUNCTION__)> x
这意味着 strlen(__PRETTY_FUNCTION__)
不是一个完整的常量表达式。
那么为什么strlen(__PRETTY_FUNCTION__)
不是整型常量表达式呢?是标准暗示的,还是 GCC 的错误?
__PRETTY_FUNCTION__
不标准。因此,编译器可以在不同的地方(解析时、构建 AST 时或链接时)实现它。
如果它应该在解析时实现,那么它可以是一个常量表达式(我猜这就是clang所做的)。 但是,如果它是在链接时实现的(即,编译器为它发出一个符号,链接器将解析它),它就不能是常量表达式。
我认为 GCC 使用后一种情况。
请注意,在这种情况下,您可以采用其中的 sizeof(),因为如果您需要编译时常量字符串的长度计算,它是 const char[]
。
因此,将表达式 3 替换为:
X<sizeof(__PRETTY_FUNCTION__) - 1> x;
它在两个编译器上都能正常编译。
编辑:正如 NathanOliver 指出的那样,GCC 似乎将 __PRETTY_FUNCTION__
的签名视为 static const char[]
,而 clang/visual 工作室将其视为 static constexpr const char[]
。这在 GCC 中是一个令人痛苦的麻烦(不是错误,因为它不是标准的)并且他们似乎已经在 >8.0.0 版本中修复了它。
在表达式(1)和表达式(2)中,__PRETTY_FUNCTION__
被衰减为const char*
(指针是常量,但数据什么也说不出来)。对我来说,表达式 (2) 可能无法证明任何事情,因为不能保证等式两边的指针应该相等,即使它们指向 "same" 内容。 string_view
构造函数期望 const char*
,因此除了 __PRETTY_FUNCTION__
之外的任何可能衰减到 const char*
的东西都会传递表达式 (2).
我仍然担心 (2)
行,所以我想出了一些不适合发表评论的内容。
我稍微修改了一下代码片段,请参考this:
#include <type_traits>
#include <string_view>
constexpr std::size_t strlen(char const* s) {
std::size_t n = 0;
while (*s++ != '[=10=]')
++n;
return n;
}
template <std::size_t>
struct X {};
static char const PF[] = "int main()";
int main() {
constexpr auto pf = std::string_view(__PRETTY_FUNCTION__); // gcc ok; clang ok; (1)
static_assert(pf == std::string_view("int main()")); // gcc ok; clang ok; (2)
X<strlen(__PRETTY_FUNCTION__)> x; // gcc not ok; clang ok; (3)
static_assert(pf[0] == 'i'); // not ok; (4)
X<pf.size()> x1; // ok; (5)
X<strlen(pf.data())> x2; // not ok; (6)
static_assert(__builtin_constant_p(pf.data()) == 0); // ok (7)
static_assert(__builtin_strlen(pf.data()) == 10); // ok (8)
static_assert(__builtin_memcmp(pf.data(), "int main()", __builtin_strlen(pf.data())) == 0); // ok (9)
static_assert(std::char_traits<char>::compare(pf.data(), "int main()", std::char_traits<char>::length(pf.data())) == 0); // ok (10)
static_assert(std::char_traits<char>::length(PF) == 10); // not ok (11)
static_assert(__builtin_strlen(PF) == 10); // not ok (12)
}
据我所知,static_assert
将无法证明 (2)
的值,如果它不是 believe
表达式是 constexpr(如行 (4)
).但是尽管 (2)
和 (4)
行有问题,(5)
行对 GCC 来说似乎没问题。所以,我偷看了std::char_traits<char>
。这些特征使用 __builtin_constant_p
在 __builtin_*
实现和像我的 strlen
这样的实现之间进行调度。 (7)
行声明 "the constexprness of the __PRETTY_FUNCTION__
can't be proven"(请参阅 gcc doc),但尽管如此,表达式 __builtin_memcmp(__PRETTY_FUNCTION__)
仍可在编译时求值(请参阅 (8)
行)。
在 (11)
和 (12)
中考虑失败,可以得出结论,在某些情况下 __PRETTY_FUNCTION__
的行为就好像它被声明为 static constexpr char const []
而在其他情况下作为 static char const []
。换句话说,如果 __PRETTY_FUNCTION__
有一个类型,那么它在编译的所有步骤中都不一致(我指的是 GCC 8.3)。