Bug 92193 - Poor diagnostics when a constexpr function call follows a failed static_assert
Summary: Poor diagnostics when a constexpr function call follows a failed static_assert
Status: RESOLVED FIXED
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 10.0
: P3 normal
Target Milestone: 12.0
Assignee: Jason Merrill
URL:
Keywords: diagnostic
Depends on:
Blocks: constexpr
  Show dependency treegraph
 
Reported: 2019-10-23 12:14 UTC by Jonathan Wakely
Modified: 2021-08-31 18:38 UTC (History)
2 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2019-11-29 00:00:00


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Jonathan Wakely 2019-10-23 12:14:28 UTC
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.
Comment 1 Jonathan Wakely 2019-11-29 16:10:06 UTC
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.
Comment 2 Jonathan Wakely 2021-08-26 23:09:58 UTC
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.
Comment 3 GCC Commits 2021-08-31 16:17:45 UTC
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.
Comment 4 Jason Merrill 2021-08-31 18:38:12 UTC
Fixed for GCC 12.