【问题标题】:__PRETTY_FUNCTION__ in constant expression__PRETTY_FUNCTION__ 在常量表达式中
【发布时间】:2019-09-14 22:17:54
【问题描述】:

请参考这个sn-p:

#include <type_traits>
#include <string_view>

constexpr std::size_t strlen(char const* s) {
    std::size_t n = 0;
    while (*s++ != '\0')
        ++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&lt;strlen(__PRETTY_FUNCTION__)&gt; x,这意味着strlen(__PRETTY_FUNCTION__) 不是一个整数常量表达式。

那么为什么strlen(__PRETTY_FUNCTION__) 不是一个整数常量表达式呢?它是标准暗示的,还是 GCC 的错误?

【问题讨论】:

  • afaik __PRETTY_FUNCTION__ 不是标准 C++
  • 注意:如果将__PRETTY_FUNCTION__ 替换为定义为helloconstexpr char hello[] = "Hello, World!";,则可以:godbolt.org/z/mKw4HK
  • @user463035818 我明白这一点,但它是如何声明的:比如char const []constexpr char const []。似乎任何一种方式都不适用。
  • 我只是说它是一个扩展而不是一个错误:P
  • 看起来他们正在努力:gcc.gnu.org/bugzilla/show_bug.cgi?id=66639

标签: c++ c++17 constant-expression


【解决方案1】:

__PRETTY_FUNCTION__ 不是标准的。因此,编译器可以在不同的地方实现它(解析时、构建 AST 时或链接时)。

如果它应该在解析时实现,那么它可以是一个常量表达式(我猜这就是 clang 正在做的事情)。 但是,如果它是在链接时实现的(即编译器为它发出一个符号,链接器会解析它),它就不能是一个常量表达式。

我认为 GCC 使用后一种情况。

请注意,在这种情况下,您可以取其中的一个 sizeof(),因为如果您需要编译时常量字符串的长度计算,它是一个 const char[]。 所以将表达式 3 替换为:

X<sizeof(__PRETTY_FUNCTION__) - 1> x;

它会在两个编译器上编译得很好。

编辑:正如 NathanOliver 指出的那样,似乎 GCC 将__PRETTY_FUNCTION__ 的签名视为static const char[],而clang/visual studio 将其视为static constexpr const char[]。这在 GCC 中是一个令人痛苦的麻烦(不是错误,因为它不是标准的),他们似乎已经在 >8.0.0 版本中修复了它。

在表达式(1)和表达式(2)中,__PRETTY_FUNCTION__ 被衰减为const char*(指针是常量,但对数据无话可说)。对我来说,表达式 (2) 可能无法证明任何事情,因为不能保证指针在等式两边都应该相等,即使它们指向“相同”的内容。 string_view 构造函数需要const char*,因此除__PRETTY_FUNCTION__ 之外的任何可能衰减到const char* 的东西都会传递表达式(2)。

【讨论】:

  • 但仍有疑问:为什么在指令 (1) 和 (2) 中它可以用作 constexpr 而在 (3) 中是不可能的?
  • @max66 不要引用我的话,但如果它是一个带有内联初始化程序的静态 const char 数组(GCC 错误线程说它是),那么你应该能够实例化一个 constexpr指针。但这并不意味着您可以以所有 constexpr 方式使用它,例如在其上运行 strlen
  • 表达式 (1) 没有告诉您 __PRETTY_FUNCTION__ 是什么,因为它可以转换/衰减,表达式 (2) 也是如此。我已经对其进行了测试,GCC 似乎将其视为static const char [],而 clang 将其视为static constexpr char[]
  • “可以接受的通知”->“不能接受的通知”?无论是那个还是“在那种情况下”都需要澄清
  • 我的意思是X&lt;sizeof(__PRETTY_FUNCTION__) - 1&gt; y; 在两个编译器中都能正常编译。但我同意,这不方便。
【解决方案2】:

我仍然担心(2) 这行,所以我想出了一些不适合评论的东西。

我稍微修改了sn-p,请参考this

#include <type_traits>
#include <string_view>

constexpr std::size_t strlen(char const* s) {
    std::size_t n = 0;
    while (*s++ != '\0')
        ++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&lt;char&gt;。特征使用__builtin_constant_p__builtin_* 实现和像我的strlen 这样的实现之间分派。 (7) 行声明“__PRETTY_FUNCTION__ 的 constexprness 无法被证明”(参见 gcc doc),但尽管如此,表达式 __builtin_memcmp(__PRETTY_FUNCTION__) 可以在编译时计算(参见 (8) 行)。

(11)(12) 中考虑失败,可以得出结论,在某些情况下__PRETTY_FUNCTION__ 的行为就像它被声明为static constexpr char const [] 而在其他情况下为static char const []。换句话说,如果__PRETTY_FUNCTION__ 有一个类型,那么它在编译的所有步骤中都是不一致的(我指的是 GCC 8.3)。

【讨论】:

  • 第 (4) 行是预期的。如果你有const char a[] = { 1, 2, 3 },即使1 是一个常量,但a[0]*(a+0),因此它不是一个常量表达式。如果您有constexpr const char a[] = { 1, 2, 3 },那么a[0] 是一个常量表达式,可以在编译时计算。这里第 (1) 行中的autoconst char* 而不是const char[],因此pf[0] 要求取消引用可能指向非常量字符的常量指针,因此无法在编译时计算.
  • 对,但我在想为什么string_view 能够在编译时比较相同的东西。答案是__builtin_memcmp
  • 您的结论完全正确。如果您查看@NathanOliver 报告的错误中的错误报告历史记录,您会看到该错误首先由允许行 (9) 的提交关闭,但后来修改为实际允许 (3)。这意味着需要查询pf 类型的构造会看到const char [],而使用该值的构造实际上会看到它的行为类似于constexpr const char[]
猜你喜欢
  • 1970-01-01
  • 2014-07-04
  • 1970-01-01
  • 2013-03-22
  • 2017-08-20
  • 1970-01-01
  • 1970-01-01
  • 2016-02-28
  • 1970-01-01
相关资源
最近更新 更多