Why does __builtin_constant_p evaluate to false for a constexpr std::string_view argument?

537 Views Asked by At

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?

1

There are 1 best solutions below

1
PluginPenguin On

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.