Description
Undefined behaviour when calling a library function is unspecified ([expr.const]p(6.1)). However, it is strange that whether it is detected depends on if libc++ is built with assertions or not.
Consider https://godbolt.org/z/Kx6ez5no5:
#include <vector>
constexpr int f() {
std::vector<int> v;
v.reserve(1);
int& i = v.front(); // Library UB
v.push_back(4);
return i;
}
static_assert(f() == 4);
This doesn't compile with assertions:
<source>:11:15: error: static assertion expression is not an integral constant expression
11 | static_assert(f() == 4);
| ^~~~~~~~
/opt/compiler-explorer/clang-assertions-trunk-20240904/bin/../include/c++/v1/vector:652:5: note: subexpression not valid in a constant expression
652 | _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(!empty(), "front() called on an empty vector");
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/clang-assertions-trunk-20240904/bin/../include/c++/v1/__assert:66:71: note: expanded from macro '_LIBCPP_ASSERT_VALID_ELEMENT_ACCESS'
66 | # define _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(expression, message) _LIBCPP_ASSERT(expression, message)
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/clang-assertions-trunk-20240904/bin/../include/c++/v1/__assert:23:10: note: expanded from macro '_LIBCPP_ASSERT'
23 | : _LIBCPP_ASSERTION_HANDLER(__FILE__ ":" _LIBCPP_TOSTRING(__LINE__) ": assertion " _LIBCPP_TOSTRING( \
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
24 | expression) " failed: " message "\n"))
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/clang-assertions-trunk-20240904/bin/../include/c++/v1/__assertion_handler:33:50: note: expanded from macro '_LIBCPP_ASSERTION_HANDLER'
33 | # define _LIBCPP_ASSERTION_HANDLER(message) __builtin_verbose_trap("libc++", message)
| ^~~~~~~~~~~~~~~~~~~~~~
<source>:6:12: note: in call to 'v.front()'
6 | int& i = v.front(); // Library UB
| ^~~~~~~~~
<source>:11:15: note: in call to 'f()'
11 | static_assert(f() == 4);
| ^~~
But does compile if assertions are disabled.
I believe libstdc++ does something like this, so this fails even without assertions enabled.
This can be done with __libcpp_is_constant_evaluated
/__builtin_is_constant_evaluated
. This also removes the need for [[maybe_unused]]
for variables used just for assertions.
The downside is the compile time would to increase on non-debug builds, where LIBCPP_ASSERT(x)
goes from (void) 0
to is_constant_evaluated() && !(x) ? assert_handler(...) : (void) 0
. I haven't measured the actual impact, but I don't expect it to be much.