14

Please refer to this snippet:

#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 compiles it, but GCC 8.3 dos not. See on godbolt. GCC fails on line (3) although lines (1) and (2) are ok. If I am able to declare pf in line (1) and use __PRETTY_FUNCTION__ in static_assert it means that the expression __PRETTY_FUNCTION__ is core constant expression. And if I'm not able do declare X<strlen(__PRETTY_FUNCTION__)> x it means that strlen(__PRETTY_FUNCTION__) is not an integral constant expression.

So why strlen(__PRETTY_FUNCTION__) is not an integral constant expression? Is it implied by the standard, or is it a GCC bug?

nicolai
  • 1,140
  • 9
  • 17
  • 4
    afaik `__PRETTY_FUNCTION__` is not standard c++ – 463035818_is_not_an_ai Apr 25 '19 at 13:09
  • Note: If you replace `__PRETTY_FUNCTION__` with `hello` defined as `constexpr char hello[] = "Hello, World!";`, it works: https://godbolt.org/z/mKw4HK – YSC Apr 25 '19 at 13:17
  • @user463035818 I understand that, but how it is declared: like `char const []` or `constexpr char const []`. It seems like either way does not apply. – nicolai Apr 25 '19 at 13:18
  • I am just saying that its an extension not a bug :P – 463035818_is_not_an_ai Apr 25 '19 at 13:19
  • 4
    Looks like they are working on it: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66639 – NathanOliver Apr 25 '19 at 13:20
  • @NathanOliver thanks, now I see that GCC trunk version compiles it too https://godbolt.org/z/qXWQ0y – nicolai Apr 25 '19 at 13:22
  • @user463035818 I see what you mean: `__PRETTY_FUNCTION__` is an extension so one can't talk about it in in terms of standard. But I was concerned about lines `(1)` and `(2)`. And the answer clarifies it. Thanks. – nicolai Apr 25 '19 at 13:48

2 Answers2

6

__PRETTY_FUNCTION__ is not standard. As such a compiler can implement it in different places (while parsing, while building the AST or while linking).

If it's supposed to be implemented while parsing, then it can be a constant expression (I guess that's what clang is doing). However, if it's implemented while linking (that is, the compiler emits a symbol for it and the linker will resolve it), it can't be a constant expression.

I think GCC use the latter case.

Please notice that you can take a sizeof() of these in that case, since it's a const char[] if you need compile-time constant string's length computation. So replace expression 3 by:

X<sizeof(__PRETTY_FUNCTION__) - 1> x;

and it'll compile fine on both compiler.

EDIT: As NathanOliver pointed out, it seems that GCC consider the signature of __PRETTY_FUNCTION__ as static const char[] while clang/visual studio consider it as static constexpr const char[]. This is a painful nuisance in GCC (not a bug, since it's not standard) and they seems to have fixed it in the >8.0.0 version.

In expression (1) and expression (2), __PRETTY_FUNCTION__ is decayed to a const char* (the pointer are constant, but nothing can be said about the data). For me, expression (2) might not prove anything, since there is no guarantee the pointers should be equal on both side of the equality, even if they points to the "same" content. string_view constructor expects const char*, thus anything other than __PRETTY_FUNCTION__ that could decay to const char* would pass expression (2).

xryl669
  • 3,376
  • 24
  • 47
  • 2
    But remain the doubt: why in instructions (1) and (2) it's usable as constexpr but in (3) isn't possible? – max66 Apr 25 '19 at 13:24
  • 1
    @max66 Don't quote me on this, but if it's a static const char array with an inline initialiser (which the GCC bug thread says it is) then you ought to be able to instantiate a constexpr pointer out of it. But that doesn't mean you can use it in all constexpr ways, like running `strlen` on it. – Lightness Races in Orbit Apr 25 '19 at 13:28
  • expression (1) does not tell you what `__PRETTY_FUNCTION__` is since it can be converted/decayed and so does expression (2). I've tested it and GCC seems to see it as `static const char []` while clang see it as `static constexpr char[]`. – xryl669 Apr 25 '19 at 13:30
  • "notice that you can take" -> "notice that you cannot take" ? either that or the "in that case" needs clarification – 463035818_is_not_an_ai Apr 25 '19 at 13:50
  • What I meant is `X y;` compiles fine in both compiler. But I agree, it's not convenient. – xryl669 Apr 25 '19 at 13:53
  • _" For me, expression (2) is undefined behavior"_, `std::string_view` compare their contents and is never undefined. Pointer equality comparisons are also never undefined. – Passer By Apr 25 '19 at 14:31
  • @PasserBy, right. It's not clear. In the expression `string_view(A) == string_view(B)` might be true because `A[i] == B[i]` for all i in A, but that does not mean that A points to the same address as B. Said differently, because the compiler is likely to optimize away the redundant string, having a program to rely on this is UB. – xryl669 Apr 25 '19 at 16:49
  • No, it is not. UB means a very specific thing in C++, and this is not it. – Passer By Apr 25 '19 at 17:02
  • Clearly, the expression (2) only proves the **value** of `__PRETTY_FUNCTION__` not the real **type**. Thus, it's not enough to deduce the "constexpr"-ness of it and you can't rely on this test to assert `__PRETTY_FUNCTION__` is a constexpr – xryl669 Apr 25 '19 at 17:02
  • @PasserBy. Right, I've modified the answer to remove the "UB" term. – xryl669 Apr 25 '19 at 17:03
0

I'm still concerned about the line (2), so I figured out something that doesn't fit into a comment.

I modified the snippet a little bit, please refer to 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)
}

As I understand, static_assert would fail to prove the value of (2) if it didn't believe the expression was constexpr (as in case of line (4)). But despite the faulty lines (2) and (4) the line (5) seems OK to GCC. So, I peeked in std::char_traits<char>. The traits use __builtin_constant_p to dispatch between the __builtin_* implementations and the ones like my strlen. The line (7) states "the constexprness of the __PRETTY_FUNCTION__ can't be proven" (see gcc doc), but despite of that the expression __builtin_memcmp(__PRETTY_FUNCTION__) can be evaluated at compile time (see line (8)).

Taking onto consideration fails in (11) and (12), one can conclude that in some cases __PRETTY_FUNCTION__ acts as if it was declared static constexpr char const [] and in the other cases as static char const []. In other words if __PRETTY_FUNCTION__ has a type then it is not consistent during the all steps of compilation (I refer to GCC 8.3).

nicolai
  • 1,140
  • 9
  • 17
  • line (4) is expected. If you have `const char a[] = { 1, 2, 3 }`, even if `1` is a constant but `a[0]` is `*(a+0)` and as such, is not a constant expression. If you had `constexpr const char a[] = { 1, 2, 3 }` then `a[0]` is a constexpr and can be computed at compile time. Here `auto` in line (1) is a `const char*` not `const char[]`, thus `pf[0]` is asking to dereference a constant pointer that could be pointing to a non constant char, so it can't be computed at compile time. – xryl669 Apr 26 '19 at 14:28
  • Right, but I was wandering why the `string_view` is able to compare the same thing at compile time. And the answer is `__builtin_memcmp`. – nicolai Apr 26 '19 at 14:33
  • You are completely right on your conclusions. If you look at the bug report history in the bug @NathanOliver reported, you'll see that the bug is first closed by a commit that allowed line (9), but later modified to actually allow (3). This means that constructions requiring querying the type of `pf` see a `const char []` while constructions using the value actually see it behaving like a `constexpr const char[]` – xryl669 Apr 26 '19 at 14:35