The following code compiles under g++ 9, with the flag -std=c++17, but not clang 8 with the same flag:
#include <string_view>
#include <cstdlib>
int main() {
constexpr std::string_view hello = "hello";
constexpr size_t last_l = hello.find_last_of("lo");
}
The error message is as follows:
test.cpp:8:19: error: constexpr variable 'last_l' must be initialized by a constant expression
constexpr size_t last_l = hello.find_last_of("lo");
^ ~~~~~~~~~~~~~~~~~~~~~~~~
/usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/char_traits.h:349:9: note: cast from 'void *' is not allowed in a constant expression
return static_cast<const char_type*>(__builtin_memchr(__s, __a, __n));
^
/usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/string_view.tcc:150:12: note: in call to 'find(&"lo"[0], 2, "hello"[4])'
if (traits_type::find(__str, __n, this->_M_str[__size]))
^
/usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/string_view:402:22: note: in call to '&hello->find_last_of(&"lo"[0], 18446744073709551615, 2)'
{ return this->find_last_of(__str, __pos, traits_type::length(__str)); }
^
test.cpp:8:34: note: in call to '&hello->find_last_of(&"lo"[0], 18446744073709551615)'
constexpr size_t last_l = hello.find_last_of("lo");
^
test.cpp:10:16: error: static_assert expression is not an integral constant expression
static_assert(last_l == 3);
^~~~~~~~~~~
test.cpp:10:16: note: initializer of 'last_l' is not a constant expression
test.cpp:8:19: note: declared here
constexpr size_t last_l = hello.find_last_of("lo");
^
2 errors generated.
Looking at the error message, it looks like it calls std::char_traits<char>::find, which according to cppreference should be constexpr and not a problem. However, looking at the implementation in char_traits.h, while it it marked constexpr, it does not seem to follow constexpr rules:
static _GLIBCXX17_CONSTEXPR const char_type*
find(const char_type* __s, size_t __n, const char_type& __a)
{
if (__n == 0)
return 0;
#if __cplusplus >= 201703L
if (__builtin_constant_p(__n)
&& __builtin_constant_p(__a)
&& __constant_char_array_p(__s, __n))
return __gnu_cxx::char_traits<char_type>::find(__s, __n, __a);
#endif
return static_cast<const char_type*>(__builtin_memchr(__s, __a, __n));
}
However, g++ does not seem to have a problem with that, even though it seems from the clang diagnostic message __builtin_memchr returns a void*, which is then casted to a char*, which seems to be disallowed (see bullet 14).
Following this same train of thought, the following sample also compiles with g++ but not clang:
#include <string>
int main() {
constexpr const char* asdf = "asdf";
constexpr const char* s_ptr = std::char_traits<char>::find(asdf, 2, 's');
}
With the error message:
test_traits.cpp:5:24: error: constexpr variable 's_ptr' must be initialized by a constant expression
constexpr const char* s_ptr = std::char_traits<char>::find(asdf, 2, 's');
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/char_traits.h:349:9: note: cast from 'void *' is not allowed in a constant expression
return static_cast<const char_type*>(__builtin_memchr(__s, __a, __n));
^
test_traits.cpp:5:32: note: in call to 'find(&"asdf"[0], 2, 's')'
constexpr const char* s_ptr = std::char_traits<char>::find(asdf, 2, 's');
^
1 error generated.
This seems like both a stdc++ bug, by not having std::char_traits<_>::find be properly constexpr, and a g++ bug, for compiling it.
Is there something I'm missing? Also, is there a workaround? I came across this trying to use magic enum, and would like to use it in a clang environment.
Update (6/7/19)
As pointed out by @cpplearner, it seems like clang should be taking the return __gnu_cxx::char_traits<char_type>::find(__s, __n, __a); Interestingly enough, that doesn't seem to be the problem. The following code, which is the exact same function from std::char_traits<char>::find, except without that constant branch, still compiles fine in g++, which makes me wonder if g++ is even taking that path:
#include <cstddef>
static constexpr const char*
find_noconst(const char* __s, size_t __n, const char& __a)
{
if (__n == 0)
return 0;
return static_cast<const char*>(__builtin_memchr(__s, __a, __n));
}
int main() {
constexpr const char* asdf = "asdf";
constexpr const char* s_ptr = find_noconst(asdf, 2, 's');
}
This still compiles in g++, and fails in clang with the same error.
Update 2 (6/7/19)
More research confirms @cpplearner comment:
#include <cstddef>
#include <string>
#include <iostream>
static constexpr const char*
branch_test(const char* __s, size_t __n, const char& __a)
{
if (__n == 0)
return 0;
if (__builtin_constant_p(__n)
&& __builtin_constant_p(__a)
&& std::__constant_char_array_p(__s, __n))
return "took branch";
return "did not take branch";
}
int main() {
constexpr const char* test = branch_test("asdf", 2, 's');
std::cout << test << std::endl;
}
With gcc, this prints took branch, but with clang it prints did not take branch.
Refining it more:
#include <string>
#include <iostream>
int main() {
constexpr bool test = std::__constant_char_array_p("test", 2);
std::cout << test << std::endl;
}
Prints 0 on clang and 1 on g++.
After digging into that implementation, it seems that this is actually a clang bug. The most minimal example of difference I could find was this:
#include <iostream>
constexpr bool is_const_char(const char* a, size_t idx) {
return __builtin_constant_p(a[idx]);
}
int main() {
constexpr bool test = is_const_char("test", 0);
std::cout << test << std::endl;
}
Prints 0 on clang and 1 on gcc. The most interesting part to me, is what actually works:
#include <iostream>
int main() {
constexpr const char* str = "test";
constexpr bool test1 = __builtin_constant_p(str[0]);
constexpr bool test2 = __builtin_constant_p("test"[0]);
std::cout << test1 << ", " << test2 << std::endl;
}
That program consistently prints 1, 1 on both clang and g++.