0

I need to interface with an API that expects compile time static strings. It enforces them via using

static_assert (__builtin_constant_p (str));

To be more specific, I'm referring to the Apple Signpost API here.

Now I have a situation where I'd like to pass a constexpr string to this interface, which however fails due to a compiler error. It boils down to this simple test case

constexpr std::string_view s = "foo";
static_assert (__builtin_constant_p (s));

which fails at the static_assert with Apple Clang 14. Now I wonder why this is the case? In their documentation on builtins, clang refers to GCC documentation, which states

You can use the built-in function __builtin_constant_p to determine if a value is known to be constant at compile time [...]

To my understanding a constexpr value is known to be constant at compile time. Why does this assertion still fail then?

Adriaan
  • 17,741
  • 7
  • 42
  • 75
PluginPenguin
  • 1,576
  • 11
  • 25
  • 1
    It seems clang and gcc have different opinions on whether it's a constexpr or not. https://godbolt.org/z/7oj8WznPe – Ted Lyngmo Jan 30 '23 at 17:45
  • 3
    `__builtin_constant_p` is of course a compiler extension and "_determine if a value is known to be constant at compile time_" isn't really precise enough of a definition to answer the question. The compilers could reasonably implement the built-in differently and still fit the description. If you are asking whether `s` by itself in your example would be a _constant expression_, which is clearly defined by the standard, then the answer would be that it depends on whether the two declarations appear at block scope or not. – user17732522 Jan 30 '23 at 17:58
  • 3
    Also [the GCC documentation](https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html) basically says that this is only telling you whether, at a given optimization level, the compiler knows the value at compile-time. So it doesn't seem to be really related to the concept of constant expressions at the language level at all. – user17732522 Jan 30 '23 at 18:06
  • 3
    I just realized that you wrote the _library_ is using this check. That seems simply wrong to me. It doesn't need to check this. Either the expression is a constant expression and will work as such where it is required or it will produce a compilation error if it isn't. The check will produce false positives as well as false negatives. And if one wants to verify that an expression is a constant expression up front, there are other ways to do that in the language. – user17732522 Jan 30 '23 at 18:17
  • @user17732522 This is actually used in Apple system headers. See my updated question for more details. But I get your point, thanks for your comment! – PluginPenguin Jan 31 '23 at 10:46
  • Please do not add answers to the question body itself. Instead, you should add it as an answer. [Answering your own question is allowed and even encouraged](https://stackoverflow.com/help/self-answer). – Adriaan Jan 31 '23 at 10:47
  • @Adriaan do you consider my edit as answer to the original question? I did not consider it to answer my question why the compiler behaves like that, it's more an explanation of a workaround I found. I can re-add it as answer for sure, but I thought this was against the idea of an actual answer, this is why I decided to add it as an update to illustrate the context of the question – PluginPenguin Jan 31 '23 at 10:50
  • The workaround does seem to solve your original problem, which is why I considered it to be an answer. If that triggers a follow-up question, do post that as a separate question in that case, linking back to this one for reference. If you think it doesn't answer your question, but rather gives more background information, could you rephrase it in such a way it won't be perceived as an answer? In general: there's nothing wrong with answering your own question and opening a follow-up. That way, posts remain focused and thereby searchable. – Adriaan Jan 31 '23 at 10:53

1 Answers1

1

As pointed out in the comments it seems that even GCC and clang behave different here so the conclusion is that the definition of the compiler builtin is way to loose in order to expect it to align with the definition of a C++ constant expression.

Still, I found a solution for my specific problem. Previously, I was trying to pass a constexpr string literal to the os_signpost_interval_begin macro like

constexpr std::string_view s = "foo"; // (actually a static constexpr class member)
os_signpost_interval_begin (log, id, s.data(), "begin");

This triggered a static assertion hidden deeper in the that macro since __builtin_constant_p (s.data()) evaluated to false, which then led me to create that test case presented in the original question.

In the meantime, I let my IDE inline the invocation of the os_signpost_interval_begin macro, which revealed some more context of that assertion. We find a macro like this being used

#define OS_LOG_STRING(_ns, _var, _str) \
        _Static_assert(__builtin_constant_p(_str), \
                "format/label/description argument must be a string constant"); \
        __attribute__((section("__TEXT,__oslogstring,cstring_literals"),internal_linkage)) \
        static const char _var[] __asm(OS_STRINGIFY(OS_CONCAT(LOS_##_ns, __COUNTER__))) = _str

so as far as I get it, that string literal variable is placed into a specific section of the binary where the profiler will expect it. Creating that variable this way by passing a const char* pointer as string to that macro rather than a literal would indeed not yield to code that compiles, so it seems that the assertion here makes some sense for this very particular use case.

As a workaround, I replicated the content of the macro myself inside a function template that takes a std::index_sequence of the string length and generate an assignment from a compile time static string by unpacking it character by character into an initializer list through an index sequence like

template <size_t... i>
void signpostIntervalBeginHelper (const std::index_sequence<i...>&)
{
   //...

   // sLen is a constexpr constant member available in my class which equals the length of s
   __attribute__((section("__TEXT,__oslogstring,cstring_literals"), internal_linkage)) static const char _os_name_str[sLen] __asm(OS_STRINGIFY(OS_CONCAT(LOS_##_ns, __COUNTER__))) = { s[i]... };
   
    //...
}

void signpostInvervalBegin()
{
    signpostInveralBeginHelper (std::make_index_sequence<sLen>());
}

which proved to solve my problem.

PluginPenguin
  • 1,576
  • 11
  • 25
  • The macro expansion indicates that the macro was written for C, not C++. For example `_Static_assert` would be used in C, because `static_assert` is just a macro expanding to it but requiring a header inclusion. But in C++ `static_assert` can always be used and isn't a macro. That would somewhat explain the use of `__builtin_constant_p`. The test is not supposed to test for a constant expression (which are much more limited in C than C++), but instead it is supposed to test specifically for string literals. (And I guess the assertion is there only for a nicer error message.) – user17732522 Jan 31 '23 at 20:05