If we compile this with NO_CONSTEXPR the diagnostic is fine: template<typename T> struct has_foo { static constexpr bool value = false; }; template<typename T> #ifndef NO_CONSTEXPR constexpr #endif bool foo(T t) noexcept(noexcept(t.foo())) { return t.foo(); } template<typename T> void maybe_foo(T t) { static_assert( has_foo<T>::value, "has foo" ); foo(t); } struct X { }; int main() { X x; maybe_foo(x); } ce.cc: In instantiation of ‘void maybe_foo(T) [with T = X]’: ce.cc:26:14: required from here ce.cc:17:20: error: static assertion failed: has foo static_assert( has_foo<T>::value, "has foo" ); ^~~~~~~~~~ However, when the function is constexpr the output is much worse: ce.cc: In instantiation of ‘void maybe_foo(T) [with T = X]’: ce.cc:23:14: required from here ce.cc:14:20: error: static assertion failed: has foo static_assert( has_foo<T>::value, "has foo" ); ^~~~~~~~~~ ce.cc: In instantiation of ‘constexpr bool foo(T) [with T = X]’: ce.cc:15:8: required from ‘void maybe_foo(T) [with T = X]’ ce.cc:23:14: required from here ce.cc:7:32: error: ‘struct X’ has no member named ‘foo’ foo(T t) noexcept(noexcept(t.foo())) ~~^~~ ce.cc: In instantiation of ‘constexpr bool foo(T) [with T = X]’: ce.cc:15:8: required from ‘void maybe_foo(T) [with T = X]’ ce.cc:23:14: required from here ce.cc:7:32: error: ‘struct X’ has no member named ‘foo’ ce.cc:8:14: error: ‘struct X’ has no member named ‘foo’ { return t.foo(); } ~~^~~ The errors following the static assertion are not useful. The static_assert is there precisely to make compilation fail, but it doesn't stop errors being produced from the constexpr function that follows it (even though we're in a non-constexpr function). This affects libstdc++ because adding 'constexpr' throughout the library means that suddenly we get loads more errors in the testsuite and need to tell dejagnu to ignore them.
This results in a far worse experience for users in C++20, where lots more things become constexpr. With the patch I suggested in Bug 92727 comment 4 the diagnostic in C++17 mode would be: In file included from /home/jwakely/gcc/10/include/c++/10.0.0/vector:67, from up.cc:1: /home/jwakely/gcc/10/include/c++/10.0.0/bits/stl_vector.h: In instantiation of 'void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = X; _Alloc = std::allocator<X>; std::vector<_Tp, _Alloc>::value_type = X]': up.cc:14:18: required from here /home/jwakely/gcc/10/include/c++/10.0.0/bits/stl_vector.h:1188:18: error: static assertion failed 1188 | static_assert(is_copy_constructible_v<value_type>); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ But compiling the exact same code with -std=gnu++2a gives: In file included from /home/jwakely/gcc/10/include/c++/10.0.0/vector:67, from up.cc:1: /home/jwakely/gcc/10/include/c++/10.0.0/bits/stl_vector.h: In instantiation of 'void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = X; _Alloc = std::allocator<X>; std::vector<_Tp, _Alloc>::value_type = X]': up.cc:14:18: required from here /home/jwakely/gcc/10/include/c++/10.0.0/bits/stl_vector.h:1188:18: error: static assertion failed 1188 | static_assert(is_copy_constructible_v<value_type>); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In file included from /home/jwakely/gcc/10/include/c++/10.0.0/ext/alloc_traits.h:34, from /home/jwakely/gcc/10/include/c++/10.0.0/bits/stl_uninitialized.h:67, from /home/jwakely/gcc/10/include/c++/10.0.0/vector:66, from up.cc:1: /home/jwakely/gcc/10/include/c++/10.0.0/bits/alloc_traits.h: In instantiation of 'static constexpr void std::allocator_traits<std::allocator<_Up> >::construct(std::allocator_traits<std::allocator<_Up> >::allocator_type&, _Up*, _Args&& ...) [with _Up = X; _Args = {const X&}; _Tp = X; std::allocator_traits<std::allocator<_Up> >::allocator_type = std::allocator<X>]': /home/jwakely/gcc/10/include/c++/10.0.0/bits/stl_vector.h:1193:30: required from 'void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = X; _Alloc = std::allocator<X>; std::vector<_Tp, _Alloc>::value_type = X]' up.cc:14:18: required from here /home/jwakely/gcc/10/include/c++/10.0.0/bits/alloc_traits.h:504:20: error: use of deleted function 'X::X(const X&)' 504 | noexcept(noexcept(::new((void*)__p) _Up(std::declval<_Args>()...))) | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ up.cc:6:7: note: 'X::X(const X&)' is implicitly deleted because the default definition would be ill-formed: 6 | class X { | ^ up.cc:6:7: error: use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = Y; _Dp = std::default_delete<Y>]' In file included from /home/jwakely/gcc/10/include/c++/10.0.0/memory:82, from up.cc:2: /home/jwakely/gcc/10/include/c++/10.0.0/bits/unique_ptr.h:456:7: note: declared here 456 | unique_ptr(const unique_ptr&) = delete; | ^~~~~~~~~~ In file included from /home/jwakely/gcc/10/include/c++/10.0.0/ext/alloc_traits.h:34, from /home/jwakely/gcc/10/include/c++/10.0.0/bits/stl_uninitialized.h:67, from /home/jwakely/gcc/10/include/c++/10.0.0/vector:66, from up.cc:1: /home/jwakely/gcc/10/include/c++/10.0.0/bits/alloc_traits.h: In instantiation of 'static constexpr void std::allocator_traits<std::allocator<_Up> >::construct(std::allocator_traits<std::allocator<_Up> >::allocator_type&, _Up*, _Args&& ...) [with _Up = X; _Args = {const X&}; _Tp = X; std::allocator_traits<std::allocator<_Up> >::allocator_type = std::allocator<X>]': /home/jwakely/gcc/10/include/c++/10.0.0/bits/stl_vector.h:1193:30: required from 'void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = X; _Alloc = std::allocator<X>; std::vector<_Tp, _Alloc>::value_type = X]' up.cc:14:18: required from here /home/jwakely/gcc/10/include/c++/10.0.0/bits/alloc_traits.h:509:21: error: no matching function for call to 'construct_at(X*&, const X&)' 509 | std::construct_at(__p, std::forward<_Args>(__args)...); | ~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In file included from /home/jwakely/gcc/10/include/c++/10.0.0/bits/stl_tempbuf.h:60, from /home/jwakely/gcc/10/include/c++/10.0.0/bits/stl_algo.h:62, from /home/jwakely/gcc/10/include/c++/10.0.0/vector:62, from up.cc:1: /home/jwakely/gcc/10/include/c++/10.0.0/bits/stl_construct.h:94:5: note: candidate: 'template<class _Tp, class ... _Args> constexpr decltype (::new(void*(0)) _Tp) std::construct_at(_Tp*, _Args&& ...)' 94 | construct_at(_Tp* __location, _Args&&... __args) | ^~~~~~~~~~~~ /home/jwakely/gcc/10/include/c++/10.0.0/bits/stl_construct.h:94:5: note: template argument deduction/substitution failed: /home/jwakely/gcc/10/include/c++/10.0.0/bits/stl_construct.h: In substitution of 'template<class _Tp, class ... _Args> constexpr decltype (::new(void*(0)) _Tp) std::construct_at(_Tp*, _Args&& ...) [with _Tp = X; _Args = {const X&}]': /home/jwakely/gcc/10/include/c++/10.0.0/bits/alloc_traits.h:509:21: required from 'static constexpr void std::allocator_traits<std::allocator<_Up> >::construct(std::allocator_traits<std::allocator<_Up> >::allocator_type&, _Up*, _Args&& ...) [with _Up = X; _Args = {const X&}; _Tp = X; std::allocator_traits<std::allocator<_Up> >::allocator_type = std::allocator<X>]' /home/jwakely/gcc/10/include/c++/10.0.0/bits/stl_vector.h:1193:30: required from 'void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = X; _Alloc = std::allocator<X>; std::vector<_Tp, _Alloc>::value_type = X]' up.cc:14:18: required from here /home/jwakely/gcc/10/include/c++/10.0.0/bits/stl_construct.h:96:17: error: use of deleted function 'X::X(const X&)' 96 | -> decltype(::new((void*)0) _Tp(std::declval<_Args>()...)) | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ That's because in C++20 some of the code following the static assertion involves a constexpr function, which calls another constexpr function, which calls another ... and all of them get evaluated and barf out their errors. The whole point of the static_assert is to stop that happening.
This makes the diagnostics for std::vector diabolical. We hit a nice static assertion saying something like: /home/jwakely/gcc/12/include/c++/12.0.0/bits/stl_uninitialized.h:90:56: error: static assertion failed: result type must be constructible from input type And then the compiler just keeps on rollin' ... /home/jwakely/gcc/12/include/c++/12.0.0/bits/stl_construct.h:115:28: error: no matching function for call to 'construct_at(X*&, X&)' ... /home/jwakely/gcc/12/include/c++/12.0.0/bits/stl_construct.h:96:17: error: use of deleted function 'X::X(const X&)' ... /home/jwakely/gcc/12/include/c++/12.0.0/bits/stl_construct.h:119:7: error: use of deleted function 'X::X(const X&)' ... The 23_containers/vector/cons/89164.cc test produces 28 lines of errors in C++17 mode, and 159 lines of errors in C++20 mode, because everything is constexpr.
The master branch has been updated by Jason Merrill <jason@gcc.gnu.org>: https://gcc.gnu.org/g:9aeadd8c319d5d940fa4dc91a393fc2959d27719 commit r12-3258-g9aeadd8c319d5d940fa4dc91a393fc2959d27719 Author: Jason Merrill <jason@redhat.com> Date: Mon Aug 30 18:42:05 2021 -0400 c++: Improve error recovery with constexpr [PR92193] The compiler tries to limit error cascades in limit_bad_template_recursion by avoiding triggering a new instantiation from one that has caused errors. We were exempting constexpr functions from this because they can be needed for constant evaluation, but as more and more functions get marked constexpr, this becomes an over-broad category. So as suggested on IRC, this patch only exempts functions that are needed for mandatory constant evaluation. As noted in the comment, this flag doesn't particularly need to use a bit in the FUNCTION_DECL, but there were still some free. PR c++/92193 gcc/cp/ChangeLog: * cp-tree.h (FNDECL_MANIFESTLY_CONST_EVALUATED): New. * constexpr.c (cxx_eval_call_expression): Set it. * pt.c (neglectable_inst_p): Check it. gcc/testsuite/ChangeLog: * g++.dg/diagnostic/static_assert4.C: New test.
Fixed for GCC 12.