__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)。