Ping for this patch.

On Sun, Jun 01, 2025 at 11:23:01PM -0400, Patrick Palka wrote:
> On Sat, 31 May 2025, Nathaniel Shead wrote:
> 
> > On Fri, May 30, 2025 at 11:10:08AM -0400, Patrick Palka wrote:
> > > On Fri, 30 May 2025, Patrick Palka wrote:
> > > 
> > > > On Fri, 30 May 2025, Nathaniel Shead wrote:
> > > > 
> > > > > On Wed, May 28, 2025 at 02:14:06PM -0400, Patrick Palka wrote:
> > > > > > On Tue, 27 May 2025, Nathaniel Shead wrote:
> > > > > > 
> > > > > > > On Wed, Nov 27, 2024 at 11:45:40AM -0500, Patrick Palka wrote:
> > > > > > > > On Fri, 8 Nov 2024, Nathaniel Shead wrote:
> > > > > > > > 
> > > > > > > > > Does this approach seem reasonable?  I'm pretty sure that the 
> > > > > > > > > way I've
> > > > > > > > > handled the templating here is unideal but I'm not sure what 
> > > > > > > > > a neat way
> > > > > > > > > to do what I'm trying to do here would be; any comments are 
> > > > > > > > > welcome.
> > > > > > > > 
> > > > > > > > Clever approach, I like it!
> > > > > > > > 
> > > > > > > > > 
> > > > > > > > > -- >8 --
> > > > > > > > > 
> > > > > > > > > Currently, concept failures of standard type traits just 
> > > > > > > > > report
> > > > > > > > > 'expression X<T> evaluates to false'.  However, many type 
> > > > > > > > > traits are
> > > > > > > > > actually defined in terms of compiler builtins; we can do 
> > > > > > > > > better here.
> > > > > > > > > For instance, 'is_constructible_v' could go on to explain why 
> > > > > > > > > the type
> > > > > > > > > is not constructible, or 'is_invocable_v' could list potential
> > > > > > > > > candidates.
> > > > > > > > 
> > > > > > > > That'd be great improvement.
> > > > > > > > 
> > > > > > > > > 
> > > > > > > > > As a first step to supporting that we need to be able to map 
> > > > > > > > > the
> > > > > > > > > standard type traits to the builtins that they use.  Rather 
> > > > > > > > > than adding
> > > > > > > > > another list that would need to be kept up-to-date whenever a 
> > > > > > > > > builtin is
> > > > > > > > > added, this patch instead tries to detect any variable 
> > > > > > > > > template defined
> > > > > > > > > directly in terms of a TRAIT_EXPR.
> > > > > > > > > 
> > > > > > > > > To avoid false positives, we ignore any variable templates 
> > > > > > > > > that have any
> > > > > > > > > specialisations (partial or explicit), even if we wouldn't 
> > > > > > > > > have chosen
> > > > > > > > > that specialisation anyway.  This shouldn't affect any of the 
> > > > > > > > > standard
> > > > > > > > > library type traits that I could see.
> > > > > > > > 
> > > > > > > > You should be able to tsubst the TEMPLATE_ID_EXPR directly and 
> > > > > > > > look at
> > > > > > > > its TI_PARTIAL_INFO in order to determine which (if any) partial
> > > > > > > > specialization was selected.  And if an explicit specialization 
> > > > > > > > was
> > > > > > > > selected the resulting VAR_DECL will have 
> > > > > > > > DECL_TEMPLATE_SPECIALIZATION
> > > > > > > > set.
> > > > > > > > 
> > > > > > > > > ...[snip]...
> > > > > > > > 
> > > > > > > > If we substituted the TEMPLATE_ID_EXPR as a whole we could use 
> > > > > > > > the
> > > > > > > > DECL_TI_ARGS of that IIUC?
> > > > > > > > 
> > > > > > > 
> > > > > > > Thanks for your comments, they were very helpful.  Here's a 
> > > > > > > totally new
> > > > > > > approach which I'm much happier with.  I've also removed the 
> > > > > > > "disable in
> > > > > > > case any specialisation exists" logic, as on further reflection I 
> > > > > > > don't
> > > > > > > imagine this to be the kind of issue I thought it might have been.
> > > > > > > 
> > > > > > > With this patch,
> > > > > > > 
> > > > > > >   template <typename T>
> > > > > > >   constexpr bool is_default_constructible_v = 
> > > > > > > __is_constructible(T);
> > > > > > > 
> > > > > > >   template <typename T>
> > > > > > >   concept default_constructible = is_default_constructible_v<T>;
> > > > > > > 
> > > > > > >   static_assert(default_constructible<void>);
> > > > > > > 
> > > > > > > now emits the following error:
> > > > > > > 
> > > > > > >   test.cpp:6:15: error: static assertion failed
> > > > > > >       6 | static_assert(default_constructible<void>);
> > > > > > >         |               ^~~~~~~~~~~~~~~~~~~~~~~~~~~
> > > > > > >   test.cpp:6:15: note: constraints not satisfied
> > > > > > >   test.cpp:4:9:   required by the constraints of ‘template<class 
> > > > > > > T> concept default_constructible’
> > > > > > >   test.cpp:4:33: note:   ‘void’ is not default constructible
> > > > > > >       4 | concept default_constructible = 
> > > > > > > is_default_constructible_v<T>;
> > > > > > >         |                                 
> > > > > > > ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> > > > > > > 
> > > > > > > There's still a lot of improvements to be made in this area, I 
> > > > > > > think:
> > > > > > > 
> > > > > > > - I haven't yet looked into updating the specific diagnostics 
> > > > > > > emitted by
> > > > > > >   the traits; I'd like to try to avoid too much code duplication 
> > > > > > > with
> > > > > > >   the implementation in cp/semantics.cc.  (I also don't think the 
> > > > > > > manual
> > > > > > >   indentation at the start of the message is particularly 
> > > > > > > helpful?)
> > > > > > 
> > > > > > For is_xible / is_convertible etc, perhaps they could use a 
> > > > > > 'complain'
> > > > > > parameter that they propagate through instead of always passing 
> > > > > > tf_none,
> > > > > > similar to build_invoke?  Then we can call those predicates directly
> > > > > > from diagnose_trait_expr with complain=tf_error so that they 
> > > > > > elaborate
> > > > > > why they failed.
> > > > > > 
> > > > > 
> > > > > Done; for is_xible I ended up slightly preferring a 'bool explain'
> > > > > (since it doesn't really make sense to talk about "complaining" for a
> > > > > predicate?) but happy to swap over if that's more consistent.
> > > > > 
> > > > > > Agreed about the extra indentation
> > > > > > 
> > > > > > > 
> > > > > > > - The message doesn't print the mapping '[with T = void]'; I 
> > > > > > > tried a
> > > > > > >   couple of things but this doesn't currently look especially
> > > > > > >   straight-forward, as we don't currently associate the args with 
> > > > > > > the
> > > > > > >   normalised atomic constraint of the declaration.
> > > > > > 
> > > > > > Maybe we can still print the
> > > > > > 
> > > > > >  note: the expression ‘normal<T> [with T = void]’ evaluated to 
> > > > > > ‘false’
> > > > > > 
> > > > > > note alongside the extended diagnostics?  Which would mean moving 
> > > > > > the
> > > > > > maybe_diagnose_standard_trait call a bit lower in
> > > > > > diagnose_atomic_constraint.
> > > > > > 
> > > > > > This would arguably make the diagnostic even noiser, but IMHO the
> > > > > > parameter mapping is an important piece of information to omit.
> > > > > > 
> > > > > 
> > > > > Agreed, can always look at condensing things later but I think this 
> > > > > is a
> > > > > good improvement.
> > > > > 
> > > > > > > 
> > > > > > > - Just generally I think there's still a lot of noise in the 
> > > > > > > diagnostic,
> > > > > > >   and I find the back-to-front ordering of 'required by...' 
> > > > > > > confusing.
> > > > > > 
> > > > > > Agreed in general, but in the case of these type trait diagnostics I
> > > > > > think noisiness is kind of inevitable especially if we start 
> > > > > > explaining
> > > > > > in detail why the trait is unsatisified (as e.g. PR117294 requests).
> > > > > > 
> > > > > 
> > > > > Thanks for your comments!  With this patch, the following example:
> > > > > 
> > > > >   #include <type_traits>
> > > > >   #include <concepts>
> > > > >   struct S {
> > > > >     S(int);
> > > > >   };
> > > > >   static_assert(std::is_nothrow_constructible_v<S, int>);
> > > > >   template <std::regular T> void foo(T t);
> > > > >   int main() {
> > > > >     foo(S{});
> > > > >   }
> > > > > 
> > > > > with '-fdiagnostics-set-output=text:experimental-nesting=yes' now 
> > > > > emits:
> > > > > 
> > > > > test.cpp:6:20: error: static assertion failed
> > > > >     6 | static_assert(std::is_nothrow_constructible_v<S, int>);
> > > > >       |               ~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> > > > >   • ‘S’ is not nothrow constructible from ‘int’, because
> > > > >   • ‘S::S(int)’ is not ‘noexcept’
> > > > >     test.cpp:4:3:
> > > > >         4 |   S(int);
> > > > >           |   ^
> > > > > test.cpp: In function ‘int main()’:
> > > > > test.cpp:9:6: error: no matching function for call to ‘foo(S)’
> > > > >     9 |   foo(S{123});
> > > > >       |   ~~~^~~~~~~~
> > > > >   • there is 1 candidate
> > > > >     • candidate 1: ‘template<class T>  requires  regular<T> void 
> > > > > foo(T)’
> > > > >       test.cpp:7:32:
> > > > >           7 | template <std::regular T> void foo(T t);
> > > > >             |                                ^~~
> > > > >       • template argument deduction/substitution failed:
> > > > >         • constraints not satisfied
> > > > > In file included from test.cpp:2:
> > > > >           • /home/wreien/.local/include/c++/16.0.0/concepts: In 
> > > > > substitution of ‘template<class T>  requires  regular<T> void foo(T) 
> > > > > [with T = S]’:
> > > > >           • required from here
> > > > >             test.cpp:9:6:
> > > > >                 9 |   foo(S{123});
> > > > >                   |   ~~~^~~~~~~~
> > > > >           • required for the satisfaction of 
> > > > > ‘constructible_from<_Tp>’ [with _Tp = S]
> > > > >             /home/wreien/.local/include/c++/16.0.0/concepts:161:13:
> > > > >               161 |     concept constructible_from
> > > > >                   |             ^~~~~~~~~~~~~~~~~~
> > > > >           • required for the satisfaction of 
> > > > > ‘default_initializable<_Tp>’ [with _Tp = S]
> > > > >             /home/wreien/.local/include/c++/16.0.0/concepts:166:13:
> > > > >               166 |     concept default_initializable = 
> > > > > constructible_from<_Tp>
> > > > >                   |             ^~~~~~~~~~~~~~~~~~~~~
> > > > >           • required for the satisfaction of ‘semiregular<_Tp>’ [with 
> > > > > _Tp = S]
> > > > >             /home/wreien/.local/include/c++/16.0.0/concepts:282:13:
> > > > >               282 |     concept semiregular = copyable<_Tp> && 
> > > > > default_initializable<_Tp>;
> > > > >                   |             ^~~~~~~~~~~
> > > > >           • required for the satisfaction of ‘regular<T>’ [with T = S]
> > > > >             /home/wreien/.local/include/c++/16.0.0/concepts:356:13:
> > > > >               356 |     concept regular = semiregular<_Tp> && 
> > > > > equality_comparable<_Tp>;
> > > > >                   |             ^~~~~~~
> > > > >           • the expression ‘is_constructible_v<_Tp, _Args ...> [with 
> > > > > _Tp = S; _Args = {}]’ evaluated to ‘false’
> > > > >             /home/wreien/.local/include/c++/16.0.0/concepts:162:30:
> > > > >               162 |       = destructible<_Tp> && 
> > > > > is_constructible_v<_Tp, _Args...>;
> > > > >                   |                              
> > > > > ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> > > > >           • ‘S’ is not default constructible, because
> > > > >           • error: no matching function for call to ‘S::S()’
> > > > >             • there are 3 candidates
> > > > >               • candidate 1: ‘S::S(int)’
> > > > >                 test.cpp:4:3:
> > > > >                     4 |   S(int);
> > > > >                       |   ^
> > > > >                 • candidate expects 1 argument, 0 provided
> > > > >               • candidate 2: ‘constexpr S::S(const S&)’
> > > > >                 test.cpp:3:8:
> > > > >                     3 | struct S {
> > > > >                       |        ^
> > > > >                 • candidate expects 1 argument, 0 provided
> > > > >               • candidate 3: ‘constexpr S::S(S&&)’
> > > > >                 • candidate expects 1 argument, 0 provided
> > > > 
> > > > Awesome, I think users will like this very much!
> > > > 
> > > > > 
> > > > > Bootstrapped and regtested on x86_64-pc-linux-gnu, OK for trunk?
> > > > > 
> > > > > -- >8 --
> > > > > 
> > > > > Currently, concept failures of standard type traits just report
> > > > > 'expression X<T> evaluates to false'.  However, many type traits are
> > > > > actually defined in terms of compiler builtins; we can do better here.
> > > > > For instance, 'is_constructible_v' could go on to explain why the type
> > > > > is not constructible, or 'is_invocable_v' could list potential
> > > > > candidates.
> > > > > 
> > > > > Apart from concept diagnostics, this is also useful when using such
> > > > > traits in a 'static_assert' directly, so this patch also adjusts the
> > > > > diagnostics in that context.
> > > > > 
> > > > > As a first step to supporting that we need to be able to map the
> > > > > standard type traits to the builtins that they use.  Rather than 
> > > > > adding
> > > > > another list that would need to be kept up-to-date whenever a builtin 
> > > > > is
> > > > > added, this patch instead tries to detect any variable template 
> > > > > defined
> > > > > directly in terms of a TRAIT_EXPR.
> > > > > 
> > > > > This patch also adjusts 'diagnose_trait_expr' to provide more helpful
> > > > > diagnostics for these cases.  Not all type traits have yet been 
> > > > > updated,
> > > > > this patch just updates those that seem particularly valuable or
> > > > > straight-forward.  The function also gets moved to cp/semantics.cc to 
> > > > > be
> > > > > closer to 'trait_expr_value'.
> > > > > 
> > > > > Various other parts of the compiler are also adjusted here to assist 
> > > > > in
> > > > > making clear diagnostics, such as making more use of 'is_stub_object' 
> > > > > to
> > > > > refer to a type directly rather than in terms of 'std::declval<T>()'.
> > > > > Additionally, since there are now more cases of nesting within a
> > > > > 'static_assert'ion I felt it was helpful for the experimental-nesting
> > > > > mode to nest here as well.
> > > > > 
> > > > >       PR c++/117294
> > > > >       PR c++/113854
> > > > > 
> > > > > gcc/cp/ChangeLog:
> > > > > 
> > > > >       * call.cc (implicit_conversion_error): Hide label when printing
> > > > >       a stub object.
> > > > >       (convert_like_internal): Likewise, and nest candidate
> > > > >       diagnostics.
> > > > >       * constexpr.cc (diagnose_failing_condition): Nest diagnostics,
> > > > >       attempt to provide more helpful diagnostics for traits.
> > > > >       * constraint.cc (diagnose_trait_expr): Move to semantics.cc.
> > > > >       (diagnose_atomic_constraint): Attempt to provide more helpful
> > > > >       diagnostics for more traits.
> > > > >       * cp-tree.h (explain_not_noexcept): Declare new function.
> > > > >       (is_trivially_xible): Add parameter.
> > > > >       (is_nothrow_xible): Likewise.
> > > > >       (is_xible): Likewise.
> > > > >       (is_convertible): Likewise.
> > > > >       (is_nothrow_convertible): Likewise.
> > > > >       (maybe_diagnose_standard_trait): Declare new function.
> > > > >       * error.cc (dump_type) <case TREE_VEC>: Handle trait types.
> > > > >       * except.cc (explain_not_noexcept): New function.
> > > > >       * method.cc (build_trait_object): Add complain parameter.
> > > > >       (assignable_expr): Add explain parameter to show diagnostics.
> > > > >       (constructible_expr): Likewise.
> > > > >       (destructible_expr): Likewise.
> > > > >       (is_xible_helper): Replace trivial flag with explain flag,
> > > > >       add diagnostics.
> > > > >       (is_trivially_xible): New explain flag.
> > > > >       (is_nothrow_xible): Likewise.
> > > > >       (is_xible): Likewise.
> > > > >       (is_convertible_helper): Add complain flag.
> > > > >       (is_convertible): New explain flag.
> > > > >       (is_nothrow_convertible): Likewise.
> > > > >       * semantics.cc (diagnose_trait_expr): Move from constrain.cc.
> > > > >       Adjust diagnostics for clarity and detail.
> > > > >       (maybe_diagnose_standard_trait): New function.
> > > > >       * typeck.cc (cp_build_function_call_vec): Add handling for stub
> > > > >       objects.
> > > > >       (convert_arguments): Always return -1 on error.
> > > > >       * typeck2.cc (cxx_readonly_error): Add handling for stub
> > > > >       objects.
> > > > > 
> > > > > libstdc++-v3/ChangeLog:
> > > > > 
> > > > >       * testsuite/20_util/any/misc/any_cast_neg.cc: Adjust
> > > > >       diagnostics.
> > > > >       * testsuite/20_util/expected/illformed_neg.cc: Likewise.
> > > > >       * testsuite/20_util/optional/monadic/or_else_neg.cc: Likewise.
> > > > >       * testsuite/23_containers/array/creation/3_neg.cc: Likewise.
> > > > >       * testsuite/24_iterators/range_generators/lwg3900.cc: Likewise.
> > > > >       * testsuite/29_atomics/atomic/requirements/types_neg.cc:
> > > > >       Likewise.
> > > > >       * 
> > > > > testsuite/30_threads/stop_token/stop_callback/invocable_neg.cc:
> > > > >       Likewise.
> > > > >       * testsuite/std/format/arguments/args_neg.cc: Likewise.
> > > > >       * testsuite/std/format/string_neg.cc: Likewise.
> > > > > 
> > > > > gcc/testsuite/ChangeLog:
> > > > > 
> > > > >       * g++.dg/cpp2a/concepts-traits3.C: Adjust diagnostics.
> > > > >       * g++.dg/cpp2a/concepts-traits4.C: New test.
> > > > >       * g++.dg/diagnostic/static_assert5.C: New test.
> > > > >       * g++.dg/ext/has_virtual_destructor2.C: New test.
> > > > >       * g++.dg/ext/is_assignable2.C: New test.
> > > > >       * g++.dg/ext/is_constructible9.C: New test.
> > > > >       * g++.dg/ext/is_convertible7.C: New test.
> > > > >       * g++.dg/ext/is_destructible2.C: New test.
> > > > >       * g++.dg/ext/is_invocable5.C: New test.
> > > > >       * g++.dg/ext/is_virtual_base_of_diagnostic2.C: New test.
> > > > > 
> > > > > Signed-off-by: Nathaniel Shead <nathanielosh...@gmail.com>
> > > > > Reviewed-by: Patrick Palka <ppa...@redhat.com>
> > > > > ---
> > > > >  gcc/cp/call.cc                                |  20 +-
> > > > >  gcc/cp/constexpr.cc                           |   5 +
> > > > >  gcc/cp/constraint.cc                          | 249 +--------------
> > > > >  gcc/cp/cp-tree.h                              |  15 +-
> > > > >  gcc/cp/error.cc                               |  14 +
> > > > >  gcc/cp/except.cc                              |  12 +
> > > > >  gcc/cp/method.cc                              | 194 ++++++++----
> > > > >  gcc/cp/semantics.cc                           | 297 
> > > > > ++++++++++++++++++
> > > > >  gcc/cp/typeck.cc                              |  14 +-
> > > > >  gcc/cp/typeck2.cc                             |   5 +
> > > > >  gcc/testsuite/g++.dg/cpp2a/concepts-traits3.C |  31 +-
> > > > >  gcc/testsuite/g++.dg/cpp2a/concepts-traits4.C |  77 +++++
> > > > >  .../g++.dg/diagnostic/static_assert5.C        |  70 +++++
> > > > >  .../g++.dg/ext/has_virtual_destructor2.C      |  27 ++
> > > > >  gcc/testsuite/g++.dg/ext/is_assignable2.C     |  36 +++
> > > > >  gcc/testsuite/g++.dg/ext/is_constructible9.C  |  60 ++++
> > > > >  gcc/testsuite/g++.dg/ext/is_convertible7.C    |  29 ++
> > > > >  gcc/testsuite/g++.dg/ext/is_destructible2.C   |  48 +++
> > > > >  gcc/testsuite/g++.dg/ext/is_invocable5.C      |  41 +++
> > > > >  .../ext/is_virtual_base_of_diagnostic2.C      |  13 +
> > > > >  .../20_util/any/misc/any_cast_neg.cc          |   3 +
> > > > >  .../20_util/expected/illformed_neg.cc         |   1 +
> > > > >  .../20_util/optional/monadic/or_else_neg.cc   |   1 +
> > > > >  .../23_containers/array/creation/3_neg.cc     |   2 +
> > > > >  .../24_iterators/range_generators/lwg3900.cc  |   3 +-
> > > > >  .../atomic/requirements/types_neg.cc          |   2 +
> > > > >  .../stop_token/stop_callback/invocable_neg.cc |   1 +
> > > > >  .../std/format/arguments/args_neg.cc          |   1 +
> > > > >  .../testsuite/std/format/string_neg.cc        |   2 +
> > > > >  29 files changed, 955 insertions(+), 318 deletions(-)
> > > > >  create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-traits4.C
> > > > >  create mode 100644 gcc/testsuite/g++.dg/diagnostic/static_assert5.C
> > > > >  create mode 100644 gcc/testsuite/g++.dg/ext/has_virtual_destructor2.C
> > > > >  create mode 100644 gcc/testsuite/g++.dg/ext/is_assignable2.C
> > > > >  create mode 100644 gcc/testsuite/g++.dg/ext/is_constructible9.C
> > > > >  create mode 100644 gcc/testsuite/g++.dg/ext/is_convertible7.C
> > > > >  create mode 100644 gcc/testsuite/g++.dg/ext/is_destructible2.C
> > > > >  create mode 100644 gcc/testsuite/g++.dg/ext/is_invocable5.C
> > > > >  create mode 100644 
> > > > > gcc/testsuite/g++.dg/ext/is_virtual_base_of_diagnostic2.C
> > > > > 
> > > > > diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
> > > > > index 2c3ef3dfc35..f5b39e403c5 100644
> > > > > --- a/gcc/cp/call.cc
> > > > > +++ b/gcc/cp/call.cc
> > > > > @@ -4876,6 +4876,11 @@ implicit_conversion_error (location_t loc, 
> > > > > tree type, tree expr)
> > > > >          && !CP_AGGREGATE_TYPE_P (type))
> > > > >      error_at (loc, "designated initializers cannot be used with a "
> > > > >             "non-aggregate type %qT", type);
> > > > > +  else if (is_stub_object (expr))
> > > > > +    /* The expression is generated by a trait check, we don't have
> > > > > +       a useful location to highlight the label.  */
> > > > > +    error_at (loc, "could not convert %qH to %qI",
> > > > > +           TREE_TYPE (expr), type);
> > > > >    else
> > > > >      {
> > > > >        range_label_for_type_mismatch label (TREE_TYPE (expr), type);
> > > > > @@ -8645,6 +8650,7 @@ convert_like_internal (conversion *convs, tree 
> > > > > expr, tree fn, int argnum,
> > > > >    diagnostic_t diag_kind;
> > > > >    int flags;
> > > > >    location_t loc = cp_expr_loc_or_input_loc (expr);
> > > > > +  const bool stub_object_p = is_stub_object (expr);
> > > > >  
> > > > >    if (convs->bad_p && !(complain & tf_error))
> > > > >      return error_mark_node;
> > > > > @@ -8721,7 +8727,10 @@ convert_like_internal (conversion *convs, tree 
> > > > > expr, tree fn, int argnum,
> > > > >                                     "from %qH to %qI", TREE_TYPE 
> > > > > (expr),
> > > > >                                     totype);
> > > > >             if (complained)
> > > > > -             print_z_candidate (loc, N_("candidate is:"), t->cand);
> > > > > +             {
> > > > > +               auto_diagnostic_nesting_level sentinel;
> > > > > +               print_z_candidate (loc, N_("candidate is:"), t->cand);
> > > > > +             }
> > > > >             expr = convert_like (t, expr, fn, argnum,
> > > > >                                  /*issue_conversion_warnings=*/false,
> > > > >                                  /*c_cast_p=*/false, 
> > > > > /*nested_p=*/true,
> > > > > @@ -8746,7 +8755,14 @@ convert_like_internal (conversion *convs, tree 
> > > > > expr, tree fn, int argnum,
> > > > >         else if (t->kind == ck_identity)
> > > > >           break;
> > > > >       }
> > > > > -      if (!complained && expr != error_mark_node)
> > > > > +      if (!complained && stub_object_p)
> > > > > +     {
> > > > > +       /* An error diagnosed within a trait, don't give extra 
> > > > > labels.  */
> > > > > +       error_at (loc, "invalid conversion from %qH to %qI",
> > > > > +                 TREE_TYPE (expr), totype);
> > > > > +       complained = 1;
> > > > > +     }
> > > > > +      else if (!complained && expr != error_mark_node)
> > > > >       {
> > > > >         range_label_for_type_mismatch label (TREE_TYPE (expr), 
> > > > > totype);
> > > > >         gcc_rich_location richloc (loc, &label, 
> > > > > highlight_colors::percent_h);
> > > > > diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
> > > > > index fa754b9a176..95b7f385dbf 100644
> > > > > --- a/gcc/cp/constexpr.cc
> > > > > +++ b/gcc/cp/constexpr.cc
> > > > > @@ -2122,10 +2122,15 @@ diagnose_failing_condition (tree bad, 
> > > > > location_t cloc, bool show_expr_p,
> > > > >    if (TREE_CODE (bad) == CLEANUP_POINT_EXPR)
> > > > >      bad = TREE_OPERAND (bad, 0);
> > > > >  
> > > > > +  auto_diagnostic_nesting_level sentinel;
> > > > > +
> > > > >    /* Actually explain the failure if this is a concept check or a
> > > > >       requires-expression.  */
> > > > >    if (concept_check_p (bad) || TREE_CODE (bad) == REQUIRES_EXPR)
> > > > >      diagnose_constraints (cloc, bad, NULL_TREE);
> > > > > +  /* Similarly if this is a standard trait.  */
> > > > > +  else if (maybe_diagnose_standard_trait (cloc, bad, NULL_TREE))
> > > > > +    ;
> > > > >    else if (COMPARISON_CLASS_P (bad)
> > > > >          && ARITHMETIC_TYPE_P (TREE_TYPE (TREE_OPERAND (bad, 0))))
> > > > >      {
> > > > > diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> > > > > index 90625707043..bb32c8ab111 100644
> > > > > --- a/gcc/cp/constraint.cc
> > > > > +++ b/gcc/cp/constraint.cc
> > > > > @@ -3013,233 +3013,6 @@ get_constraint_error_location (tree t)
> > > > >    return input_location;
> > > > >  }
> > > > >  
> > > > > -/* Emit a diagnostic for a failed trait.  */
> > > > > -
> > > > > -static void
> > > > > -diagnose_trait_expr (tree expr, tree args)
> > > > > -{
> > > > > -  location_t loc = cp_expr_location (expr);
> > > > > -
> > > > > -  /* Build a "fake" version of the instantiated trait, so we can
> > > > > -     get the instantiated types from result.  */
> > > > > -  ++processing_template_decl;
> > > > > -  expr = tsubst_expr (expr, args, tf_none, NULL_TREE);
> > > > > -  --processing_template_decl;
> > > > > -
> > > > > -  tree t1 = TRAIT_EXPR_TYPE1 (expr);
> > > > > -  tree t2 = TRAIT_EXPR_TYPE2 (expr);
> > > > > -  if (t2 && TREE_CODE (t2) == TREE_VEC)
> > > > > -    {
> > > > > -      /* Convert the TREE_VEC of arguments into a TREE_LIST, since 
> > > > > we can't
> > > > > -      directly print a TREE_VEC but we can a TREE_LIST via the E 
> > > > > format
> > > > > -      specifier.  */
> > > > > -      tree list = NULL_TREE;
> > > > > -      for (tree t : tree_vec_range (t2))
> > > > > -     list = tree_cons (NULL_TREE, t, list);
> > > > > -      t2 = nreverse (list);
> > > > > -    }
> > > > > -  switch (TRAIT_EXPR_KIND (expr))
> > > > > -    {
> > > > > -    case CPTK_HAS_NOTHROW_ASSIGN:
> > > > > -      inform (loc, "  %qT is not nothrow copy assignable", t1);
> > > > > -      break;
> > > > > -    case CPTK_HAS_NOTHROW_CONSTRUCTOR:
> > > > > -      inform (loc, "  %qT is not nothrow default constructible", t1);
> > > > > -      break;
> > > > > -    case CPTK_HAS_NOTHROW_COPY:
> > > > > -      inform (loc, "  %qT is not nothrow copy constructible", t1);
> > > > > -      break;
> > > > > -    case CPTK_HAS_TRIVIAL_ASSIGN:
> > > > > -      inform (loc, "  %qT is not trivially copy assignable", t1);
> > > > > -      break;
> > > > > -    case CPTK_HAS_TRIVIAL_CONSTRUCTOR:
> > > > > -      inform (loc, "  %qT is not trivially default constructible", 
> > > > > t1);
> > > > > -      break;
> > > > > -    case CPTK_HAS_TRIVIAL_COPY:
> > > > > -      inform (loc, "  %qT is not trivially copy constructible", t1);
> > > > > -      break;
> > > > > -    case CPTK_HAS_TRIVIAL_DESTRUCTOR:
> > > > > -      inform (loc, "  %qT is not trivially destructible", t1);
> > > > > -      break;
> > > > > -    case CPTK_HAS_UNIQUE_OBJ_REPRESENTATIONS:
> > > > > -      inform (loc, "  %qT does not have unique object 
> > > > > representations", t1);
> > > > > -      break;
> > > > > -    case CPTK_HAS_VIRTUAL_DESTRUCTOR:
> > > > > -      inform (loc, "  %qT does not have a virtual destructor", t1);
> > > > > -      break;
> > > > > -    case CPTK_IS_ABSTRACT:
> > > > > -      inform (loc, "  %qT is not an abstract class", t1);
> > > > > -      break;
> > > > > -    case CPTK_IS_AGGREGATE:
> > > > > -      inform (loc, "  %qT is not an aggregate", t1);
> > > > > -      break;
> > > > > -    case CPTK_IS_ARRAY:
> > > > > -      inform (loc, "  %qT is not an array", t1);
> > > > > -      break;
> > > > > -    case CPTK_IS_ASSIGNABLE:
> > > > > -      inform (loc, "  %qT is not assignable from %qT", t1, t2);
> > > > > -      break;
> > > > > -    case CPTK_IS_BASE_OF:
> > > > > -      inform (loc, "  %qT is not a base of %qT", t1, t2);
> > > > > -      break;
> > > > > -    case CPTK_IS_BOUNDED_ARRAY:
> > > > > -      inform (loc, "  %qT is not a bounded array", t1);
> > > > > -      break;
> > > > > -    case CPTK_IS_CLASS:
> > > > > -      inform (loc, "  %qT is not a class", t1);
> > > > > -      break;
> > > > > -    case CPTK_IS_CONST:
> > > > > -      inform (loc, "  %qT is not a const type", t1);
> > > > > -      break;
> > > > > -    case CPTK_IS_CONSTRUCTIBLE:
> > > > > -      if (!t2)
> > > > > -     inform (loc, "  %qT is not default constructible", t1);
> > > > > -      else
> > > > > -     inform (loc, "  %qT is not constructible from %qE", t1, t2);
> > > > > -      break;
> > > > > -    case CPTK_IS_CONVERTIBLE:
> > > > > -      inform (loc, "  %qT is not convertible from %qE", t2, t1);
> > > > > -      break;
> > > > > -    case CPTK_IS_DESTRUCTIBLE:
> > > > > -      inform (loc, "  %qT is not destructible", t1);
> > > > > -      break;
> > > > > -    case CPTK_IS_EMPTY:
> > > > > -      inform (loc, "  %qT is not an empty class", t1);
> > > > > -      break;
> > > > > -    case CPTK_IS_ENUM:
> > > > > -      inform (loc, "  %qT is not an enum", t1);
> > > > > -      break;
> > > > > -    case CPTK_IS_FINAL:
> > > > > -      inform (loc, "  %qT is not a final class", t1);
> > > > > -      break;
> > > > > -    case CPTK_IS_FUNCTION:
> > > > > -      inform (loc, "  %qT is not a function", t1);
> > > > > -      break;
> > > > > -    case CPTK_IS_INVOCABLE:
> > > > > -      if (!t2)
> > > > > -     inform (loc, "  %qT is not invocable", t1);
> > > > > -      else
> > > > > -     inform (loc, "  %qT is not invocable by %qE", t1, t2);
> > > > > -      break;
> > > > > -    case CPTK_IS_LAYOUT_COMPATIBLE:
> > > > > -      inform (loc, "  %qT is not layout compatible with %qT", t1, 
> > > > > t2);
> > > > > -      break;
> > > > > -    case CPTK_IS_LITERAL_TYPE:
> > > > > -      inform (loc, "  %qT is not a literal type", t1);
> > > > > -      break;
> > > > > -    case CPTK_IS_MEMBER_FUNCTION_POINTER:
> > > > > -      inform (loc, "  %qT is not a member function pointer", t1);
> > > > > -      break;
> > > > > -    case CPTK_IS_MEMBER_OBJECT_POINTER:
> > > > > -      inform (loc, "  %qT is not a member object pointer", t1);
> > > > > -      break;
> > > > > -    case CPTK_IS_MEMBER_POINTER:
> > > > > -      inform (loc, "  %qT is not a member pointer", t1);
> > > > > -      break;
> > > > > -    case CPTK_IS_NOTHROW_ASSIGNABLE:
> > > > > -      inform (loc, "  %qT is not nothrow assignable from %qT", t1, 
> > > > > t2);
> > > > > -      break;
> > > > > -    case CPTK_IS_NOTHROW_CONSTRUCTIBLE:
> > > > > -      if (!t2)
> > > > > -     inform (loc, "  %qT is not nothrow default constructible", t1);
> > > > > -      else
> > > > > -     inform (loc, "  %qT is not nothrow constructible from %qE", t1, 
> > > > > t2);
> > > > > -      break;
> > > > > -    case CPTK_IS_NOTHROW_CONVERTIBLE:
> > > > > -      inform (loc, "  %qT is not nothrow convertible from %qE", t2, 
> > > > > t1);
> > > > > -      break;
> > > > > -    case CPTK_IS_NOTHROW_DESTRUCTIBLE:
> > > > > -      inform (loc, "  %qT is not nothrow destructible", t1);
> > > > > -      break;
> > > > > -    case CPTK_IS_NOTHROW_INVOCABLE:
> > > > > -      if (!t2)
> > > > > -     inform (loc, "  %qT is not nothrow invocable", t1);
> > > > > -      else
> > > > > -     inform (loc, "  %qT is not nothrow invocable by %qE", t1, t2);
> > > > > -      break;
> > > > > -    case CPTK_IS_OBJECT:
> > > > > -      inform (loc, "  %qT is not an object type", t1);
> > > > > -      break;
> > > > > -    case CPTK_IS_POINTER_INTERCONVERTIBLE_BASE_OF:
> > > > > -      inform (loc, "  %qT is not pointer-interconvertible base of 
> > > > > %qT",
> > > > > -           t1, t2);
> > > > > -      break;
> > > > > -    case CPTK_IS_POD:
> > > > > -      inform (loc, "  %qT is not a POD type", t1);
> > > > > -      break;
> > > > > -    case CPTK_IS_POINTER:
> > > > > -      inform (loc, "  %qT is not a pointer", t1);
> > > > > -      break;
> > > > > -    case CPTK_IS_POLYMORPHIC:
> > > > > -      inform (loc, "  %qT is not a polymorphic type", t1);
> > > > > -      break;
> > > > > -    case CPTK_IS_REFERENCE:
> > > > > -      inform (loc, "  %qT is not a reference", t1);
> > > > > -      break;
> > > > > -    case CPTK_IS_SAME:
> > > > > -      inform (loc, "  %qT is not the same as %qT", t1, t2);
> > > > > -      break;
> > > > > -    case CPTK_IS_SCOPED_ENUM:
> > > > > -      inform (loc, "  %qT is not a scoped enum", t1);
> > > > > -      break;
> > > > > -    case CPTK_IS_STD_LAYOUT:
> > > > > -      inform (loc, "  %qT is not an standard layout type", t1);
> > > > > -      break;
> > > > > -    case CPTK_IS_TRIVIAL:
> > > > > -      inform (loc, "  %qT is not a trivial type", t1);
> > > > > -      break;
> > > > > -    case CPTK_IS_TRIVIALLY_ASSIGNABLE:
> > > > > -      inform (loc, "  %qT is not trivially assignable from %qT", t1, 
> > > > > t2);
> > > > > -      break;
> > > > > -    case CPTK_IS_TRIVIALLY_CONSTRUCTIBLE:
> > > > > -      if (!t2)
> > > > > -     inform (loc, "  %qT is not trivially default constructible", 
> > > > > t1);
> > > > > -      else
> > > > > -     inform (loc, "  %qT is not trivially constructible from %qE", 
> > > > > t1, t2);
> > > > > -      break;
> > > > > -    case CPTK_IS_TRIVIALLY_COPYABLE:
> > > > > -      inform (loc, "  %qT is not trivially copyable", t1);
> > > > > -      break;
> > > > > -    case CPTK_IS_TRIVIALLY_DESTRUCTIBLE:
> > > > > -      inform (loc, "  %qT is not trivially destructible", t1);
> > > > > -      break;
> > > > > -    case CPTK_IS_UNBOUNDED_ARRAY:
> > > > > -      inform (loc, "  %qT is not an unbounded array", t1);
> > > > > -      break;
> > > > > -    case CPTK_IS_UNION:
> > > > > -      inform (loc, "  %qT is not a union", t1);
> > > > > -      break;
> > > > > -    case CPTK_IS_VIRTUAL_BASE_OF:
> > > > > -      inform (loc, "  %qT is not a virtual base of %qT", t1, t2);
> > > > > -      break;
> > > > > -    case CPTK_IS_VOLATILE:
> > > > > -      inform (loc, "  %qT is not a volatile type", t1);
> > > > > -      break;
> > > > > -    case CPTK_RANK:
> > > > > -      inform (loc, "  %qT cannot yield a rank", t1);
> > > > > -      break;
> > > > > -    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
> > > > > -      inform (loc, "  %qT is not a reference that binds to a 
> > > > > temporary "
> > > > > -           "object of type %qT (direct-initialization)", t1, t2);
> > > > > -      break;
> > > > > -    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
> > > > > -      inform (loc, "  %qT is not a reference that binds to a 
> > > > > temporary "
> > > > > -           "object of type %qT (copy-initialization)", t1, t2);
> > > > > -      break;
> > > > > -    case CPTK_IS_DEDUCIBLE:
> > > > > -      inform (loc, "  %qD is not deducible from %qT", t1, t2);
> > > > > -      break;
> > > > > -#define DEFTRAIT_TYPE(CODE, NAME, ARITY) \
> > > > > -    case CPTK_##CODE:
> > > > > -#include "cp-trait.def"
> > > > > -#undef DEFTRAIT_TYPE
> > > > > -      /* Type-yielding traits aren't expressions.  */
> > > > > -      gcc_unreachable ();
> > > > > -    /* We deliberately omit the default case so that when adding a 
> > > > > new
> > > > > -       trait we'll get reminded (by way of a warning) to handle it 
> > > > > here.  */
> > > > > -    }
> > > > > -}
> > > > > -
> > > > >  /* Diagnose a substitution failure in the atomic constraint T using 
> > > > > ARGS.  */
> > > > >  
> > > > >  static void
> > > > > @@ -3264,25 +3037,23 @@ diagnose_atomic_constraint (tree t, tree 
> > > > > args, tree result, sat_info info)
> > > > >    /* Generate better diagnostics for certain kinds of expressions.  
> > > > > */
> > > > >    tree expr = ATOMIC_CONSTR_EXPR (t);
> > > > >    STRIP_ANY_LOCATION_WRAPPER (expr);
> > > > > -  switch (TREE_CODE (expr))
> > > > > +
> > > > > +  if (TREE_CODE (expr) == REQUIRES_EXPR)
> > > > >      {
> > > > > -    case TRAIT_EXPR:
> > > > > -      diagnose_trait_expr (expr, args);
> > > > > -      break;
> > > > > -    case REQUIRES_EXPR:
> > > > >        gcc_checking_assert (info.diagnose_unsatisfaction_p ());
> > > > >        /* Clear in_decl before replaying the substitution to avoid 
> > > > > emitting
> > > > >        seemingly unhelpful "in declaration ..." notes that follow some
> > > > >        substitution failure error messages.  */
> > > > >        info.in_decl = NULL_TREE;
> > > > >        tsubst_requires_expr (expr, args, info);
> > > > > -      break;
> > > > > -    default:
> > > > > -      if (!same_type_p (TREE_TYPE (result), boolean_type_node))
> > > > > -     error_at (loc, "constraint %qE has type %qT, not %<bool%>",
> > > > > -               t, TREE_TYPE (result));
> > > > > -      else
> > > > > -     inform (loc, "the expression %qE evaluated to %<false%>", t);
> > > > > +    }
> > > > > +  else if (!same_type_p (TREE_TYPE (result), boolean_type_node))
> > > > > +    error_at (loc, "constraint %qE has type %qT, not %<bool%>",
> > > > > +           t, TREE_TYPE (result));
> > > > > +  else
> > > > > +    {
> > > > > +      inform (loc, "the expression %qE evaluated to %<false%>", t);
> > > > > +      maybe_diagnose_standard_trait (loc, expr, args);
> > > > >      }
> > > > >  }
> > > > >  
> > > > > diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> > > > > index 19c0b452d86..89e0d96809e 100644
> > > > > --- a/gcc/cp/cp-tree.h
> > > > > +++ b/gcc/cp/cp-tree.h
> > > > > @@ -7387,6 +7387,7 @@ extern int nothrow_libfn_p                      
> > > > > (const_tree);
> > > > >  extern void check_handlers                   (tree);
> > > > >  extern tree finish_noexcept_expr             (tree, tsubst_flags_t);
> > > > >  extern bool expr_noexcept_p                  (tree, tsubst_flags_t);
> > > > > +extern void explain_not_noexcept             (tree);
> > > > >  extern void perform_deferred_noexcept_checks (void);
> > > > >  extern bool nothrow_spec_p                   (const_tree);
> > > > >  extern bool type_noexcept_p                  (const_tree);
> > > > > @@ -7508,11 +7509,14 @@ extern void finish_thunk                      
> > > > > (tree);
> > > > >  extern void use_thunk                                (tree, bool);
> > > > >  extern bool trivial_fn_p                     (tree);
> > > > >  extern tree forward_parm                     (tree);
> > > > > -extern bool is_trivially_xible                       (enum 
> > > > > tree_code, tree, tree);
> > > > > -extern bool is_nothrow_xible                 (enum tree_code, tree, 
> > > > > tree);
> > > > > -extern bool is_xible                         (enum tree_code, tree, 
> > > > > tree);
> > > > > -extern bool is_convertible                   (tree, tree);
> > > > > -extern bool is_nothrow_convertible           (tree, tree);
> > > > > +extern bool is_trivially_xible                       (enum 
> > > > > tree_code, tree, tree,
> > > > > +                                              bool = false);
> > > > > +extern bool is_nothrow_xible                 (enum tree_code, tree, 
> > > > > tree,
> > > > > +                                              bool = false);
> > > > > +extern bool is_xible                         (enum tree_code, tree, 
> > > > > tree,
> > > > > +                                              bool = false);
> > > > > +extern bool is_convertible                   (tree, tree, bool = 
> > > > > false);
> > > > > +extern bool is_nothrow_convertible           (tree, tree, bool = 
> > > > > false);
> > > > >  extern bool ref_xes_from_temporary           (tree, tree, bool);
> > > > >  extern tree get_defaulted_eh_spec            (tree, tsubst_flags_t = 
> > > > > tf_warning_or_error);
> > > > >  extern bool maybe_explain_implicit_delete    (tree);
> > > > > @@ -8140,6 +8144,7 @@ extern void finish_static_assert                
> > > > > (tree, tree, location_t,
> > > > >  extern tree finish_decltype_type                (tree, bool, 
> > > > > tsubst_flags_t);
> > > > >  extern tree fold_builtin_is_corresponding_member (location_t, int, 
> > > > > tree *);
> > > > >  extern tree fold_builtin_is_pointer_inverconvertible_with_class 
> > > > > (location_t, int, tree *);
> > > > > +extern bool maybe_diagnose_standard_trait    (location_t, tree, 
> > > > > tree);
> > > > >  extern tree finish_trait_expr                        (location_t, 
> > > > > enum cp_trait_kind, tree, tree);
> > > > >  extern tree finish_trait_type                        (enum 
> > > > > cp_trait_kind, tree, tree, tsubst_flags_t);
> > > > >  extern tree build_lambda_expr                   (void);
> > > > > diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
> > > > > index d52dad3db29..9cf04fa4191 100644
> > > > > --- a/gcc/cp/error.cc
> > > > > +++ b/gcc/cp/error.cc
> > > > > @@ -704,6 +704,20 @@ dump_type (cxx_pretty_printer *pp, tree t, int 
> > > > > flags)
> > > > >       }
> > > > >        break;
> > > > >  
> > > > > +    case TREE_VEC:
> > > > > +      {
> > > > > +     /* A list of types used for a trait.  */
> > > > > +     bool need_comma = false;
> > > > > +     for (tree arg : tree_vec_range (t))
> > > > > +       {
> > > > > +         if (need_comma)
> > > > > +           pp_separate_with_comma (pp);
> > > > > +         dump_type (pp, arg, flags);
> > > > > +         need_comma = true;
> > > > > +       }
> > > > > +      }
> > > > > +      break;
> > > > > +
> > > > 
> > > > Is this needed because of the removed TREE_VEC -> TREE_LIST conversion
> > > > in diagnose_trait_expr?  I think I'd prefer keep performing that
> > > > conversion there, making sure to pass a TREE_LIST version of 't2' to
> > > > inform, and leave open the question of how the pretty printer should
> > > > handle TREE_VEC.
> > > > 
> > 
> > Yes; I was running into issues because the TREE_LIST handling required
> > %qE, which doesn't handle many types correctly, e.g. NULLPTR_TYPE.
> > It looked to me that rather than adding more logic to treat expressions
> > as types, it would be better to just use %qT to begin with, and
> > TREE_LIST is already used there for function parameters so I made use of
> > TREE_VEC instead, since that's what we got them as to start with.
> 
> Ah, makes sense.
> 
> > 
> > Open to any other thoughts here though; maybe %qE should become a
> > potential 'catch-all' option?
> 
> I wouldn't mind %qE becoming a catch-all.
> 
> Idealling pretty printing TREE_VEC should support mixed type/expression
> elements to handle pretty printing of arbitrary template argument lists,
> but I guess that could be a %qE feature, and %qT could assume only type
> elements as your current approach does now.  I wonder what Jason thinks.
> 
> > 
> > > > >      case TREE_LIST:
> > > > >        /* A list of function parms.  */
> > > > >        dump_parameters (pp, t, flags);
> > > > > diff --git a/gcc/cp/except.cc b/gcc/cp/except.cc
> > > > > index a9d8e2ffb57..3541a59451c 100644
> > > > > --- a/gcc/cp/except.cc
> > > > > +++ b/gcc/cp/except.cc
> > > > > @@ -1202,6 +1202,18 @@ expr_noexcept_p (tree expr, tsubst_flags_t 
> > > > > complain)
> > > > >      return true;
> > > > >  }
> > > > >  
> > > > > +/* Explain why EXPR is not noexcept.  */
> > > > > +
> > > > > +void explain_not_noexcept (tree expr)
> > > > > +{
> > > > > +  tree fn = cp_walk_tree_without_duplicates (&expr, 
> > > > > check_noexcept_r, 0);
> > > > > +  gcc_assert (fn);
> > > > > +  if (DECL_P (fn))
> > > > > +    inform (DECL_SOURCE_LOCATION (fn), "%qD is not %<noexcept%>", 
> > > > > fn);
> > > > > +  else
> > > > > +    inform (location_of (fn), "%qT is not %<noexcept%>", TREE_TYPE 
> > > > > (fn));
> > > > > +}
> > > > > +
> > > > >  /* Return true iff SPEC is throw() or noexcept(true).  */
> > > > >  
> > > > >  bool
> > > > > diff --git a/gcc/cp/method.cc b/gcc/cp/method.cc
> > > > > index 3a675d9f872..de28fc2fd1e 100644
> > > > > --- a/gcc/cp/method.cc
> > > > > +++ b/gcc/cp/method.cc
> > > > > @@ -1928,8 +1928,8 @@ is_stub_object (tree expr)
> > > > >  
> > > > >  /* Build a std::declval<TYPE>() expression and return it.  */
> > > > >  
> > > > > -tree
> > > > > -build_trait_object (tree type)
> > > > > +static tree
> > > > > +build_trait_object (tree type, tsubst_flags_t complain = tf_none)
> > > > 
> > > > We generally avoid defaulted 'complain' parameters, they make it easy to
> > > > forget to propagate 'complain' from the caller.
> > > > 
> > 
> > Makes sense, fixed.  ...this immediately showed me some cases I'd missed
> > where I should have been passing 'complain' from the caller!
> > 
> > > > >  {
> > > > >    /* TYPE can't be a function with cv-/ref-qualifiers: std::declval 
> > > > > is
> > > > >       defined as
> > > > > @@ -1942,7 +1942,11 @@ build_trait_object (tree type)
> > > > >    if (FUNC_OR_METHOD_TYPE_P (type)
> > > > >        && (type_memfn_quals (type) != TYPE_UNQUALIFIED
> > > > >         || type_memfn_rqual (type) != REF_QUAL_NONE))
> > > > > -    return error_mark_node;
> > > > > +    {
> > > > > +      if (complain & tf_error)
> > > > > +     error ("%qT is a qualified function type", type);
> > > > > +      return error_mark_node;
> > > > > +    }
> > > > >  
> > > > >    return build_stub_object (type);
> > > > >  }
> > > > > @@ -2227,12 +2231,20 @@ check_nontriv (tree *tp, int *, void *)
> > > > >  /* Return declval<T>() = declval<U>() treated as an unevaluated 
> > > > > operand.  */
> > > > >  
> > > > >  static tree
> > > > > -assignable_expr (tree to, tree from)
> > > > > +assignable_expr (tree to, tree from, bool explain)
> > > > >  {
> > > > >    cp_unevaluated cp_uneval_guard;
> > > > > -  to = build_trait_object (to);
> > > > > -  from = build_trait_object (from);
> > > > > -  tree r = cp_build_modify_expr (input_location, to, NOP_EXPR, from, 
> > > > > tf_none);
> > > > > +  tsubst_flags_t complain = explain ? tf_error : tf_none;
> > > > > +
> > > > > +  to = build_trait_object (to, complain);
> > > > > +  if (to == error_mark_node)
> > > > > +    return error_mark_node;
> > > > > +
> > > > > +  from = build_trait_object (from, complain);
> > > > > +  if (from == error_mark_node)
> > > > > +    return error_mark_node;
> > > > > +
> > > > > +  tree r = cp_build_modify_expr (input_location, to, NOP_EXPR, from, 
> > > > > complain);
> > > > >    return r;
> > > > >  }
> > > > >  
> > > > > @@ -2244,10 +2256,11 @@ assignable_expr (tree to, tree from)
> > > > >     Return something equivalent in well-formedness and triviality.  */
> > > > >  
> > > > >  static tree
> > > > > -constructible_expr (tree to, tree from)
> > > > > +constructible_expr (tree to, tree from, bool explain)
> > > > >  {
> > > > >    tree expr;
> > > > >    cp_unevaluated cp_uneval_guard;
> > > > > +  tsubst_flags_t complain = explain ? tf_error : tf_none;
> > > > >    const int len = TREE_VEC_LENGTH (from);
> > > > >    if (CLASS_TYPE_P (to))
> > > > >      {
> > > > > @@ -2257,14 +2270,14 @@ constructible_expr (tree to, tree from)
> > > > >       to = cp_build_reference_type (to, /*rval*/false);
> > > > >        tree ob = build_stub_object (to);
> > > > >        if (len == 0)
> > > > > -     expr = build_value_init (ctype, tf_none);
> > > > > +     expr = build_value_init (ctype, complain);
> > > > >        else
> > > > >       {
> > > > >         vec_alloc (args, len);
> > > > >         for (tree arg : tree_vec_range (from))
> > > > >           args->quick_push (build_stub_object (arg));
> > > > >         expr = build_special_member_call (ob, 
> > > > > complete_ctor_identifier, &args,
> > > > > -                                         ctype, LOOKUP_NORMAL, 
> > > > > tf_none);
> > > > > +                                         ctype, LOOKUP_NORMAL, 
> > > > > complain);
> > > > >       }
> > > > >        if (expr == error_mark_node)
> > > > >       return error_mark_node;
> > > > > @@ -2274,7 +2287,7 @@ constructible_expr (tree to, tree from)
> > > > >       {
> > > > >         tree dtor = build_special_member_call (ob, 
> > > > > complete_dtor_identifier,
> > > > >                                                NULL, ctype, 
> > > > > LOOKUP_NORMAL,
> > > > > -                                              tf_none);
> > > > > +                                              complain);
> > > > >         if (dtor == error_mark_node)
> > > > >           return error_mark_node;
> > > > >         if (!TYPE_HAS_TRIVIAL_DESTRUCTOR (ctype))
> > > > > @@ -2284,12 +2297,15 @@ constructible_expr (tree to, tree from)
> > > > >    else
> > > > >      {
> > > > >        if (len == 0)
> > > > > -     return build_value_init (strip_array_types (to), tf_none);
> > > > > +     return build_value_init (strip_array_types (to), complain);
> > > > >        if (len > 1)
> > > > >       {
> > > > >         if (cxx_dialect < cxx20)
> > > > > -         /* Too many initializers.  */
> > > > > -         return error_mark_node;
> > > > > +         {
> > > > > +           if (explain)
> > > > > +             error ("too many initializers for non-class type %qT", 
> > > > > to);
> > > > > +           return error_mark_node;
> > > > > +         }
> > > > >  
> > > > >         /* In C++20 this is well-formed:
> > > > >              using T = int[2];
> > > > > @@ -2310,9 +2326,11 @@ constructible_expr (tree to, tree from)
> > > > >       }
> > > > >        else
> > > > >       from = build_stub_object (TREE_VEC_ELT (from, 0));
> > > > > +
> > > > > +      tree orig_from = from;
> > > > >        expr = perform_direct_initialization_if_possible (to, from,
> > > > >                                                       /*cast*/false,
> > > > > -                                                     tf_none);
> > > > > +                                                     complain);
> > > > >        /* If t(e) didn't work, maybe t{e} will.  */
> > > > >        if (expr == NULL_TREE
> > > > >         && len == 1
> > > > > @@ -2324,7 +2342,24 @@ constructible_expr (tree to, tree from)
> > > > >         CONSTRUCTOR_IS_PAREN_INIT (from) = true;
> > > > >         expr = perform_direct_initialization_if_possible (to, from,
> > > > >                                                           
> > > > > /*cast*/false,
> > > > > -                                                         tf_none);
> > > > > +                                                         complain);
> > > > > +     }
> > > > > +
> > > > > +      if (expr == NULL_TREE && explain)
> > > > > +     {
> > > > > +       if (len > 1)
> > > > > +         error ("too many initializers for non-class type %qT", to);
> > > > > +       else
> > > > > +         {
> > > > > +           /* Redo the implicit conversion for diagnostics.  */
> > > > > +           int count = errorcount + warningcount;
> > > > > +           perform_implicit_conversion_flags (to, orig_from, 
> > > > > complain,
> > > > > +                                              LOOKUP_NORMAL);
> > > > > +           if (count == errorcount + warningcount)
> > > > > +             /* The message may have been suppressed due to -w + 
> > > > > -fpermissive,
> > > > > +                emit a generic response instead.  */
> > > > > +             error ("the conversion is invalid");
> > > > > +         }
> > > > >       }
> > > > >      }
> > > > >    return expr;
> > > > > @@ -2333,82 +2368,118 @@ constructible_expr (tree to, tree from)
> > > > >  /* Return declval<T>().~T() treated as an unevaluated operand.  */
> > > > >  
> > > > >  static tree
> > > > > -destructible_expr (tree to)
> > > > > +destructible_expr (tree to, bool explain)
> > > > >  {
> > > > >    cp_unevaluated cp_uneval_guard;
> > > > > +  tsubst_flags_t complain = explain ? tf_error : tf_none;
> > > > >    int flags = LOOKUP_NORMAL|LOOKUP_DESTRUCTOR;
> > > > > -  to = build_trait_object (to);
> > > > > +  to = build_trait_object (to, complain);
> > > > >    tree r = build_delete (input_location, TREE_TYPE (to), to,
> > > > > -                      sfk_complete_destructor, flags, 0, tf_none);
> > > > > +                      sfk_complete_destructor, flags, 0, complain);
> > > > >    return r;
> > > > >  }
> > > > >  
> > > > >  /* Returns a tree iff TO is assignable (if CODE is MODIFY_EXPR) or
> > > > >     constructible (otherwise) from FROM, which is a single type for
> > > > > -   assignment or a list of types for construction.  */
> > > > > +   assignment or a list of types for construction.  If EXPLAIN is
> > > > > +   set, emit a diagnostic explaining why the operation failed.  */
> > > > >  
> > > > >  static tree
> > > > > -is_xible_helper (enum tree_code code, tree to, tree from, bool 
> > > > > trivial)
> > > > > +is_xible_helper (enum tree_code code, tree to, tree from, bool 
> > > > > explain)
> > > > >  {
> > > > >    to = complete_type (to);
> > > > >    deferring_access_check_sentinel acs (dk_no_deferred);
> > > > > -  if (VOID_TYPE_P (to) || ABSTRACT_CLASS_TYPE_P (to)
> > > > > -      || (from && FUNC_OR_METHOD_TYPE_P (from)
> > > > > -       && (TYPE_READONLY (from) || FUNCTION_REF_QUALIFIED (from))))
> > > > > -    return error_mark_node;
> > > > > +
> > > > > +  if (!COMPLETE_TYPE_P (to))
> > > > > +    {
> > > > > +      if (explain)
> > > > > +     error_at (location_of (to), "%qT is incomplete", to);
> > > > > +      return error_mark_node;
> > > > > +    }
> > > > > +  if (ABSTRACT_CLASS_TYPE_P (to))
> > > > > +    {
> > > > > +      if (explain)
> > > > > +     error_at (location_of (to), "%qT is abstract", to);
> > > > > +      return error_mark_node;
> > > > > +    }
> > > > > +  if (from
> > > > > +      && FUNC_OR_METHOD_TYPE_P (from)
> > > > > +      && (TYPE_READONLY (from) || FUNCTION_REF_QUALIFIED (from)))
> > > > > +    {
> > > > > +      if (explain)
> > > > > +     error ("%qT is a qualified function type", from);
> > > > > +      return error_mark_node;
> > > > > +    }
> > > > > +
> > > > >    tree expr;
> > > > >    if (code == MODIFY_EXPR)
> > > > > -    expr = assignable_expr (to, from);
> > > > > +    expr = assignable_expr (to, from, explain);
> > > > >    else if (code == BIT_NOT_EXPR)
> > > > > -    expr = destructible_expr (to);
> > > > > -  else if (trivial && TREE_VEC_LENGTH (from) > 1
> > > > > -        && cxx_dialect < cxx20)
> > > > > -    return error_mark_node; // only 0- and 1-argument ctors can be 
> > > > > trivial
> > > > > -                         // before C++20 aggregate paren init
> > > > > +    expr = destructible_expr (to, explain);
> > > > >    else if (TREE_CODE (to) == ARRAY_TYPE && !TYPE_DOMAIN (to))
> > > > > -    return error_mark_node; // can't construct an array of unknown 
> > > > > bound
> > > > > +    {
> > > > > +      if (explain)
> > > > > +     error ("cannot construct an array of unknown bound");
> > > > > +      return error_mark_node;
> > > > > +    }
> > > > >    else
> > > > > -    expr = constructible_expr (to, from);
> > > > > +    expr = constructible_expr (to, from, explain);
> > > > >    return expr;
> > > > >  }
> > > > >  
> > > > >  /* Returns true iff TO is trivially assignable (if CODE is 
> > > > > MODIFY_EXPR) or
> > > > >     constructible (otherwise) from FROM, which is a single type for
> > > > > -   assignment or a list of types for construction.  */
> > > > > +   assignment or a list of types for construction.  If EXPLAIN, 
> > > > > diagnose
> > > > > +   why we returned false.  */
> > > > >  
> > > > >  bool
> > > > > -is_trivially_xible (enum tree_code code, tree to, tree from)
> > > > > +is_trivially_xible (enum tree_code code, tree to, tree from,
> > > > > +                 bool explain/*=false*/)
> > > > 
> > > > IMHO these defaulted explain parameters are fine since they're
> > > > specialized predicates that will only have a handful of callers,
> > > > unlike build_trait_object.
> > > > 
> > > > >  {
> > > > > -  tree expr = is_xible_helper (code, to, from, /*trivial*/true);
> > > > > +  tree expr = is_xible_helper (code, to, from, explain);
> > > > >    if (expr == NULL_TREE || expr == error_mark_node)
> > > > >      return false;
> > > > >    tree nt = cp_walk_tree_without_duplicates (&expr, check_nontriv, 
> > > > > NULL);
> > > > > +  if (explain)
> > > > > +    {
> > > > > +      gcc_assert (nt);
> > > > > +      inform (location_of (nt), "%qE is non-trivial", nt);
> > > > > +    }
> > > > >    return !nt;
> > > > >  }
> > > > >  
> > > > >  /* Returns true iff TO is nothrow assignable (if CODE is 
> > > > > MODIFY_EXPR) or
> > > > >     constructible (otherwise) from FROM, which is a single type for
> > > > > -   assignment or a list of types for construction.  */
> > > > > +   assignment or a list of types for construction.  If EXPLAIN, 
> > > > > diagnose
> > > > > +   why we returned false.  */
> > > > >  
> > > > >  bool
> > > > > -is_nothrow_xible (enum tree_code code, tree to, tree from)
> > > > > +is_nothrow_xible (enum tree_code code, tree to, tree from,
> > > > > +               bool explain/*=false*/)
> > > > >  {
> > > > >    ++cp_noexcept_operand;
> > > > > -  tree expr = is_xible_helper (code, to, from, /*trivial*/false);
> > > > > +  tree expr = is_xible_helper (code, to, from, explain);
> > > > >    --cp_noexcept_operand;
> > > > >    if (expr == NULL_TREE || expr == error_mark_node)
> > > > >      return false;
> > > > > -  return expr_noexcept_p (expr, tf_none);
> > > > > +  bool is_noexcept = expr_noexcept_p (expr, tf_none);
> > > > > +  if (explain)
> > > > > +    {
> > > > > +      gcc_assert (!is_noexcept);
> > > > > +      explain_not_noexcept (expr);
> > > > > +    }
> > > > > +  return is_noexcept;
> > > > >  }
> > > > >  
> > > > >  /* Returns true iff TO is assignable (if CODE is MODIFY_EXPR) or
> > > > >     constructible (otherwise) from FROM, which is a single type for
> > > > > -   assignment or a list of types for construction.  */
> > > > > +   assignment or a list of types for construction.  If EXPLAIN, 
> > > > > diagnose
> > > > > +   why we returned false.  */
> > > > >  
> > > > >  bool
> > > > > -is_xible (enum tree_code code, tree to, tree from)
> > > > > +is_xible (enum tree_code code, tree to, tree from, bool 
> > > > > explain/*=false*/)
> > > > >  {
> > > > > -  tree expr = is_xible_helper (code, to, from, /*trivial*/false);
> > > > > +  tree expr = is_xible_helper (code, to, from, explain);
> > > > >    if (expr == error_mark_node)
> > > > >      return false;
> > > > >    return !!expr;
> > > > > @@ -2442,25 +2513,36 @@ ref_xes_from_temporary (tree to, tree from, 
> > > > > bool direct_init_p)
> > > > >  }
> > > > >  
> > > > >  /* Worker for is_{,nothrow_}convertible.  Attempt to perform an 
> > > > > implicit
> > > > > -   conversion from FROM to TO and return the result.  */
> > > > > +   conversion from FROM to TO and return the result.  If EXPLAIN, 
> > > > > emit a
> > > > > +   diagnostic about why the conversion failed.  */
> > > > >  
> > > > >  static tree
> > > > > -is_convertible_helper (tree from, tree to)
> > > > > +is_convertible_helper (tree from, tree to, bool explain)
> > > > >  {
> > > > >    if (VOID_TYPE_P (from) && VOID_TYPE_P (to))
> > > > >      return integer_one_node;
> > > > >    cp_unevaluated u;
> > > > > -  tree expr = build_trait_object (from);
> > > > > +  tsubst_flags_t complain = explain ? tf_error : tf_none;
> > > > > +
> > > > >    /* std::is_{,nothrow_}convertible test whether the imaginary 
> > > > > function
> > > > >       definition
> > > > >  
> > > > >         To test() { return std::declval<From>(); }
> > > > >  
> > > > >       is well-formed.  A function can't return a function.  */
> > > > > -  if (FUNC_OR_METHOD_TYPE_P (to) || expr == error_mark_node)
> > > > > +  if (FUNC_OR_METHOD_TYPE_P (to))
> > > > > +    {
> > > > > +      if (explain)
> > > > > +     error ("%qT is a function type", to);
> > > > > +      return error_mark_node;
> > > > > +    }
> > > > > +
> > > > > +  tree expr = build_trait_object (from, complain);
> > > > > +  if (expr == error_mark_node)
> > > > >      return error_mark_node;
> > > > > +
> > > > >    deferring_access_check_sentinel acs (dk_no_deferred);
> > > > > -  return perform_implicit_conversion (to, expr, tf_none);
> > > > > +  return perform_implicit_conversion (to, expr, complain);
> > > > >  }
> > > > >  
> > > > >  /* Return true if FROM can be converted to TO using implicit 
> > > > > conversions,
> > > > > @@ -2469,9 +2551,9 @@ is_convertible_helper (tree from, tree to)
> > > > >     to either type" restriction.  */
> > > > >  
> > > > >  bool
> > > > > -is_convertible (tree from, tree to)
> > > > > +is_convertible (tree from, tree to, bool explain/*=false*/)
> > > > >  {
> > > > > -  tree expr = is_convertible_helper (from, to);
> > > > > +  tree expr = is_convertible_helper (from, to, explain);
> > > > >    if (expr == error_mark_node)
> > > > >      return false;
> > > > >    return !!expr;
> > > > > @@ -2480,12 +2562,18 @@ is_convertible (tree from, tree to)
> > > > >  /* Like is_convertible, but the conversion is also noexcept.  */
> > > > >  
> > > > >  bool
> > > > > -is_nothrow_convertible (tree from, tree to)
> > > > > +is_nothrow_convertible (tree from, tree to, bool explain/*=false*/)
> > > > >  {
> > > > > -  tree expr = is_convertible_helper (from, to);
> > > > > +  tree expr = is_convertible_helper (from, to, explain);
> > > > >    if (expr == NULL_TREE || expr == error_mark_node)
> > > > >      return false;
> > > > > -  return expr_noexcept_p (expr, tf_none);
> > > > > +  bool is_noexcept = expr_noexcept_p (expr, tf_none);
> > > > > +  if (explain)
> > > > > +    {
> > > > > +      gcc_assert (!is_noexcept);
> > > > > +      explain_not_noexcept (expr);
> > > > > +    }
> > > > > +  return is_noexcept;
> > > > >  }
> > > > >  
> > > > >  /* Categorize various special_function_kinds.  */
> > > > > diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
> > > > > index 241f2730878..bb537bef05e 100644
> > > > > --- a/gcc/cp/semantics.cc
> > > > > +++ b/gcc/cp/semantics.cc
> > > > > @@ -13426,6 +13426,303 @@ trait_expr_value (cp_trait_kind kind, tree 
> > > > > type1, tree type2)
> > > > >    gcc_unreachable ();
> > > > >  }
> > > > >  
> > > > > +/* Emit a diagnostic for a failed trait.  */
> > > > > +
> > > > > +static void
> > > > > +diagnose_trait_expr (location_t loc, tree expr, tree args)
> > > > > +{
> > > > > +  /* Build a "fake" version of the instantiated trait, so we can
> > > > > +     get the instantiated types from result.  */
> > > > > +  ++processing_template_decl;
> > > > > +  expr = tsubst_expr (expr, args, tf_none, NULL_TREE);
> > > > > +  --processing_template_decl;
> > > > > +
> > > > > +  tree t1 = TRAIT_EXPR_TYPE1 (expr);
> > > > > +  tree t2 = TRAIT_EXPR_TYPE2 (expr);
> > > > > +
> > > > > +  location_t decl_loc = location_of (t1);
> > > > > +  if (decl_loc == input_location)
> > > > > +    decl_loc = loc;
> > > > 
> > > > If we instead did
> > > > 
> > > >   if (decl_loc != input_location)
> > > >     loc = decl_loc;
> > > > 
> > > > could we always pass loc to inform instead of sometimes loc, sometimes
> > > > decl_loc?
> > > > 
> > 
> > Ah, I'd forgotten I'd done this!  I'd tried to choose between decl_loc
> > and loc based on whether I thought showing the class type would be
> > helpful.  For instance, for 'is_const_v<S>' I don't think pointing at
> > the declaration of 'S' helps (and might even be confusing), but for any
> > type traits that are intrinsically about the properties of class types
> > it would be helpful.
> 
> Makes sense.
> 
> > 
> > But there's definitely an argument to be made for consistency here too,
> > and I probably haven't even gotten the choice right for all traits here
> > yet anyway, so I'm also happy to just override with decl_loc for now;
> > thoughts?
> > 
> > > > > +
> > > > > +  switch (TRAIT_EXPR_KIND (expr))
> > > > > +    {
> > > > > +    case CPTK_HAS_NOTHROW_ASSIGN:
> > > > > +      inform (decl_loc, "%qT is not nothrow copy assignable", t1);
> > > > > +      break;
> > > > > +    case CPTK_HAS_NOTHROW_CONSTRUCTOR:
> > > > > +      inform (decl_loc, "%qT is not nothrow default constructible", 
> > > > > t1);
> > > > > +      break;
> > > > > +    case CPTK_HAS_NOTHROW_COPY:
> > > > > +      inform (decl_loc, "%qT is not nothrow copy constructible", t1);
> > > > > +      break;
> > > > > +    case CPTK_HAS_TRIVIAL_ASSIGN:
> > > > > +      inform (decl_loc, "%qT is not trivially copy assignable", t1);
> > > > > +      break;
> > > > > +    case CPTK_HAS_TRIVIAL_CONSTRUCTOR:
> > > > > +      inform (decl_loc, "%qT is not trivially default 
> > > > > constructible", t1);
> > > > > +      break;
> > > > > +    case CPTK_HAS_TRIVIAL_COPY:
> > > > > +      inform (decl_loc, "%qT is not trivially copy constructible", 
> > > > > t1);
> > > > > +      break;
> > > > > +    case CPTK_HAS_TRIVIAL_DESTRUCTOR:
> > > > > +      inform (decl_loc, "%qT is not trivially destructible", t1);
> > > > > +      break;
> > > > > +    case CPTK_HAS_UNIQUE_OBJ_REPRESENTATIONS:
> > > > > +      inform (decl_loc, "%qT does not have unique object 
> > > > > representations", t1);
> > > > > +      break;
> > > > > +    case CPTK_HAS_VIRTUAL_DESTRUCTOR:
> > > > > +      {
> > > > > +     location_t dtor_loc = decl_loc;
> > > > > +     if (NON_UNION_CLASS_TYPE_P (t1))
> > > > > +       if (tree dtor = CLASSTYPE_DESTRUCTOR (t1))
> > > > > +         dtor_loc = DECL_SOURCE_LOCATION (dtor);
> > > > > +     inform (dtor_loc, "%qT does not have a virtual destructor", t1);
> > > > > +      }
> > > > > +      break;
> > > > > +    case CPTK_IS_ABSTRACT:
> > > > > +      inform (decl_loc, "%qT is not an abstract class", t1);
> > > > > +      break;
> > > > > +    case CPTK_IS_AGGREGATE:
> > > > > +      inform (decl_loc, "%qT is not an aggregate", t1);
> > > > > +      break;
> > > > > +    case CPTK_IS_ARRAY:
> > > > > +      inform (loc, "%qT is not an array", t1);
> > > > > +      break;
> > > > > +    case CPTK_IS_ASSIGNABLE:
> > > > > +      inform (loc, "%qT is not assignable from %qT, because", t1, 
> > > > > t2);
> > > > > +      is_xible (MODIFY_EXPR, t1, t2, /*explain=*/true);
> > > > > +      break;
> > > > > +    case CPTK_IS_BASE_OF:
> > > > > +      inform (decl_loc, "%qT is not a base of %qT", t1, t2);
> > > > > +      break;
> > > > > +    case CPTK_IS_BOUNDED_ARRAY:
> > > > > +      inform (loc, "%qT is not a bounded array", t1);
> > > > > +      break;
> > > > > +    case CPTK_IS_CLASS:
> > > > > +      inform (decl_loc, "%qT is not a class", t1);
> > > > > +      break;
> > > > > +    case CPTK_IS_CONST:
> > > > > +      inform (loc, "%qT is not a const type", t1);
> > > > > +      break;
> > > > > +    case CPTK_IS_CONSTRUCTIBLE:
> > > > > +      if (!TREE_VEC_LENGTH (t2))
> > > > > +     inform (loc, "%qT is not default constructible, because", t1);
> > > > > +      else
> > > > > +     inform (loc, "%qT is not constructible from %qT, because", t1, 
> > > > > t2);
> > > > > +      is_xible (INIT_EXPR, t1, t2, /*explain=*/true);
> > > > > +      break;
> > > > > +    case CPTK_IS_CONVERTIBLE:
> > > > > +      /* The errors produced here all seem to mention "convertible" 
> > > > > in the
> > > > > +      diagnostic, so an extra inform here appears redundant.  */
> > > > > +      is_convertible (t1, t2, /*explain=*/true);
> > > > > +      break;
> > > > > +    case CPTK_IS_DESTRUCTIBLE:
> > > > > +      inform (loc, "%qT is not destructible, because", t1);
> > > > > +      is_xible (BIT_NOT_EXPR, t1, NULL_TREE, /*explain=*/true);
> > > > > +      break;
> > > > > +    case CPTK_IS_EMPTY:
> > > > > +      inform (decl_loc, "%qT is not an empty class", t1);
> > > > > +      break;
> > > > > +    case CPTK_IS_ENUM:
> > > > > +      inform (decl_loc, "%qT is not an enum", t1);
> > > > > +      break;
> > > > > +    case CPTK_IS_FINAL:
> > > > > +      inform (decl_loc, "%qT is not a final class", t1);
> > > > > +      break;
> > > > > +    case CPTK_IS_FUNCTION:
> > > > > +      inform (loc, "%qT is not a function", t1);
> > > > > +      break;
> > > > > +    case CPTK_IS_INVOCABLE:
> > > > > +      {
> > > > > +     if (!TREE_VEC_LENGTH (t2))
> > > > > +       inform (loc, "%qT is not invocable, because", t1);
> > > > > +     else
> > > > > +       inform (loc, "%qT is not invocable by %qT, because", t1, t2);
> > > > > +     tree call = build_invoke (t1, t2, tf_error);
> > > > > +     gcc_assert (call == error_mark_node);
> > > > > +      }
> > > > > +      break;
> > > > > +    case CPTK_IS_LAYOUT_COMPATIBLE:
> > > > > +      inform (loc, "%qT is not layout compatible with %qT", t1, t2);
> > > > > +      break;
> > > > > +    case CPTK_IS_LITERAL_TYPE:
> > > > > +      inform (decl_loc, "%qT is not a literal type", t1);
> > > > > +      break;
> > > > > +    case CPTK_IS_MEMBER_FUNCTION_POINTER:
> > > > > +      inform (loc, "%qT is not a member function pointer", t1);
> > > > > +      break;
> > > > > +    case CPTK_IS_MEMBER_OBJECT_POINTER:
> > > > > +      inform (loc, "%qT is not a member object pointer", t1);
> > > > > +      break;
> > > > > +    case CPTK_IS_MEMBER_POINTER:
> > > > > +      inform (loc, "%qT is not a member pointer", t1);
> > > > > +      break;
> > > > > +    case CPTK_IS_NOTHROW_ASSIGNABLE:
> > > > > +      inform (loc, "%qT is not nothrow assignable from %qT, 
> > > > > because", t1, t2);
> > > > > +      is_nothrow_xible (MODIFY_EXPR, t1, t2, /*explain=*/true);
> > > > > +      break;
> > > > > +    case CPTK_IS_NOTHROW_CONSTRUCTIBLE:
> > > > > +      if (!TREE_VEC_LENGTH (t2))
> > > > > +     inform (loc, "%qT is not nothrow default constructible, 
> > > > > because", t1);
> > > > > +      else
> > > > > +     inform (loc, "%qT is not nothrow constructible from %qT, 
> > > > > because",
> > > > > +             t1, t2);
> > > > > +      is_nothrow_xible (INIT_EXPR, t1, t2, /*explain=*/true);
> > > > > +      break;
> > > > > +    case CPTK_IS_NOTHROW_CONVERTIBLE:
> > > > > +      inform (loc, "%qT is not nothrow convertible from %qT, 
> > > > > because", t1, t2);
> > > > > +      is_nothrow_convertible (t1, t2, /*explain=*/true);
> > > > > +      break;
> > > > > +    case CPTK_IS_NOTHROW_DESTRUCTIBLE:
> > > > > +      inform (loc, "%qT is not nothrow destructible, because", t1);
> > > > > +      is_nothrow_xible (BIT_NOT_EXPR, t1, NULL_TREE, 
> > > > > /*explain=*/true);
> > > > > +      break;
> > > > > +    case CPTK_IS_NOTHROW_INVOCABLE:
> > > > > +      {
> > > > > +     if (!TREE_VEC_LENGTH (t2))
> > > > > +       inform (loc, "%qT is not nothrow invocable, because", t1);
> > > > > +     else
> > > > > +       inform (loc, "%qT is not nothrow invocable by %qT, because", 
> > > > > t1, t2);
> > > > > +     tree call = build_invoke (t1, t2, tf_error);
> > > > > +     if (call != error_mark_node)
> > > > > +       explain_not_noexcept (call);
> > > > > +      }
> > > > > +      break;
> > > > > +    case CPTK_IS_OBJECT:
> > > > > +      inform (loc, "%qT is not an object type", t1);
> > > > > +      break;
> > > > > +    case CPTK_IS_POINTER_INTERCONVERTIBLE_BASE_OF:
> > > > > +      inform (decl_loc, "%qT is not a pointer-interconvertible base 
> > > > > of %qT",
> > > > > +           t1, t2);
> > > > > +      break;
> > > > > +    case CPTK_IS_POD:
> > > > > +      inform (loc, "%qT is not a POD type", t1);
> > > > > +      break;
> > > > > +    case CPTK_IS_POINTER:
> > > > > +      inform (loc, "%qT is not a pointer", t1);
> > > > > +      break;
> > > > > +    case CPTK_IS_POLYMORPHIC:
> > > > > +      inform (decl_loc, "%qT is not a polymorphic type", t1);
> > > > > +      break;
> > > > > +    case CPTK_IS_REFERENCE:
> > > > > +      inform (loc, "%qT is not a reference", t1);
> > > > > +      break;
> > > > > +    case CPTK_IS_SAME:
> > > > > +      inform (loc, "%q#T is not the same as %q#T", t1, t2);
> > > > > +      break;
> > > > > +    case CPTK_IS_SCOPED_ENUM:
> > > > > +      inform (decl_loc, "%qT is not a scoped enum", t1);
> > > > > +      break;
> > > > > +    case CPTK_IS_STD_LAYOUT:
> > > > > +      inform (decl_loc, "%qT is not a standard layout type", t1);
> > > > > +      break;
> > > > > +    case CPTK_IS_TRIVIAL:
> > > > > +      inform (decl_loc, "%qT is not a trivial type", t1);
> > > > > +      break;
> > > > > +    case CPTK_IS_TRIVIALLY_ASSIGNABLE:
> > > > > +      inform (loc, "%qT is not trivially assignable from %qT, 
> > > > > because", t1, t2);
> > > > > +      is_trivially_xible (MODIFY_EXPR, t1, t2, /*explain=*/true);
> > > > > +      break;
> > > > > +    case CPTK_IS_TRIVIALLY_CONSTRUCTIBLE:
> > > > > +      if (!TREE_VEC_LENGTH (t2))
> > > > > +     inform (loc, "%qT is not trivially default constructible, 
> > > > > because", t1);
> > > > > +      else
> > > > > +     inform (loc, "%qT is not trivially constructible from %qT, 
> > > > > because",
> > > > > +             t1, t2);
> > > > > +      is_trivially_xible (INIT_EXPR, t1, t2, /*explain=*/true);
> > > > > +      break;
> > > > > +    case CPTK_IS_TRIVIALLY_COPYABLE:
> > > > > +      inform (decl_loc, "%qT is not trivially copyable", t1);
> > > > > +      break;
> > > > > +    case CPTK_IS_TRIVIALLY_DESTRUCTIBLE:
> > > > > +      inform (loc, "%qT is not trivially destructible, because", t1);
> > > > > +      is_trivially_xible (BIT_NOT_EXPR, t1, NULL_TREE, 
> > > > > /*explain=*/true);
> > > > > +      break;
> > > > > +    case CPTK_IS_UNBOUNDED_ARRAY:
> > > > > +      inform (loc, "%qT is not an unbounded array", t1);
> > > > > +      break;
> > > > > +    case CPTK_IS_UNION:
> > > > > +      inform (decl_loc, "%qT is not a union", t1);
> > > > > +      break;
> > > > > +    case CPTK_IS_VIRTUAL_BASE_OF:
> > > > > +      inform (decl_loc, "%qT is not a virtual base of %qT", t1, t2);
> > > > > +      if (CLASS_TYPE_P (t2))
> > > > > +     inform (location_of (t2), "%qT declared here", t2);
> > > > > +      break;
> > > > > +    case CPTK_IS_VOLATILE:
> > > > > +      inform (loc, "%qT is not a volatile type", t1);
> > > > > +      break;
> > > > > +    case CPTK_RANK:
> > > > > +      inform (loc, "%qT cannot yield a rank", t1);
> > > > > +      break;
> > > > > +    case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY:
> > > > > +      inform (loc, "%qT is not a reference that binds to a temporary 
> > > > > "
> > > > > +           "object of type %qT (direct-initialization)", t1, t2);
> > > > > +      break;
> > > > > +    case CPTK_REF_CONVERTS_FROM_TEMPORARY:
> > > > > +      inform (loc, "%qT is not a reference that binds to a temporary 
> > > > > "
> > > > > +           "object of type %qT (copy-initialization)", t1, t2);
> > > > > +      break;
> > > > > +    case CPTK_IS_DEDUCIBLE:
> > > > > +      inform (loc, "%qD is not deducible from %qT", t1, t2);
> > > > > +      break;
> > > > > +#define DEFTRAIT_TYPE(CODE, NAME, ARITY) \
> > > > > +    case CPTK_##CODE:
> > > > > +#include "cp-trait.def"
> > > > > +#undef DEFTRAIT_TYPE
> > > > > +      /* Type-yielding traits aren't expressions.  */
> > > > > +      gcc_unreachable ();
> > > > > +    /* We deliberately omit the default case so that when adding a 
> > > > > new
> > > > > +       trait we'll get reminded (by way of a warning) to handle it 
> > > > > here.  */
> > > > > +    }
> > > > > +}
> > > > > +
> > > > > +/* Attempt to detect if this is a standard type trait, defined in 
> > > > > terms
> > > > > +   of a compiler builtin (above).  If so, this will allow us to 
> > > > > provide
> > > > > +   more helpful diagnostics.   */
> > > > > +
> > > > > +bool
> > > > > +maybe_diagnose_standard_trait (location_t loc, tree expr, tree args)
> > > > > +{
> > > > > +  /* TODO: in some cases it would be possible to provide more helpful
> > > > > +     diagnostics for negations of traits, e.g. '!is_same_v<T1, T2>'. 
> > > > >  */
> > > > > +
> > > > > +  if (TREE_CODE (expr) == TEMPLATE_ID_EXPR || TREE_CODE (expr) == 
> > > > > SCOPE_REF)
> > > > > +    {
> > > > > +      processing_template_decl_sentinel ptds;
> > > > > +      expr = tsubst_expr (expr, args, tf_none, NULL_TREE);
> > > > > +      expr = tree_strip_nop_conversions (expr);
> > > > > +      args = NULL_TREE;
> > > > > +    }
> > > > 
> > > > Perhaps the callers should be responsible for performing this
> > > > substitution, rather than doing it here?  That way
> > > > maybe_diagnose_standard_trait can assume expr is non-dependent and it
> > > > can only be responsible for diagnosing.  IIUC only 
> > > > diagnose_atomic_constraint
> > > > needs to do this substitution, and not diagnose_failing_condition.
> > > 
> > > Maybe we could pass the non-constant-evaluated 'result' from
> > > satisfy_atom to diagnose_atomic_constraint, that way it doesn't have to
> > > re-do the substitution at all.
> > > 
> > 
> > Part of the ugliness is that we need the unsubstituted TRAIT_EXPR, as we
> > need to do a partial substitution in 'diagnose_trait_expr' to be able to
> > retrieve the types used for the trait (a full 'tsubst_expr' just gets us
> > back the result of the trait, either true/false).
> > 
> > Maybe a nicer way of handling this is to assume that
> > 'maybe_diagnose_standard_trait' always gets a fully substituted
> > expression (so we'll never get passed TRAIT_EXPR directly); any callers
> > who expect they might have a TRAIT_EXPR could check for that and call
> > diagnose_trait_expr themselves?
> 
> Ah, I overlooked that part of maybe_diagnose_standard_trait in v3 of
> your patch.   Yeah, we can't
> TRAIT_EXPR in v3 of your patch.  Makes sense, we can't avoid not
> substituting 
> 
> > 
> > > > 
> > > > Besides these small comments, LGTM!
> > > > 
> > 
> > Thanks very much for the review!  I've attached an updated patch; below
> > is just the diff from v3.
> > 
> > Bootstrapped and regtested (so far just dg.exp) on x86_64-pc-linux-gnu,
> > OK for trunk if full regtest succeeds?
> 
> Looks good, thanks!
> 
> > 
> > -- >8 --
> > 
> > diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
> > index 77f65655519..672d7d63d8b 100644
> > --- a/gcc/cp/constexpr.cc
> > +++ b/gcc/cp/constexpr.cc
> > @@ -2129,7 +2129,7 @@ diagnose_failing_condition (tree bad, location_t 
> > cloc, bool show_expr_p,
> >    if (concept_check_p (bad) || TREE_CODE (bad) == REQUIRES_EXPR)
> >      diagnose_constraints (cloc, bad, NULL_TREE);
> >    /* Similarly if this is a standard trait.  */
> > -  else if (maybe_diagnose_standard_trait (cloc, bad, NULL_TREE))
> > +  else if (maybe_diagnose_standard_trait (cloc, bad))
> >      ;
> >    else if (COMPARISON_CLASS_P (bad)
> >        && ARITHMETIC_TYPE_P (TREE_TYPE (TREE_OPERAND (bad, 0))))
> > diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> > index bb32c8ab111..0e5d28c347d 100644
> > --- a/gcc/cp/constraint.cc
> > +++ b/gcc/cp/constraint.cc
> > @@ -2442,10 +2442,11 @@ satisfy_atom (tree t, tree args, sat_info info)
> >    result = force_rvalue (result, info.complain);
> >    if (result == error_mark_node)
> >      return cache.save (inst_cache.save (error_mark_node));
> > +  tree substituted = result;
> >    if (!same_type_p (TREE_TYPE (result), boolean_type_node))
> >      {
> >        if (info.noisy ())
> > -   diagnose_atomic_constraint (t, args, result, info);
> > +   diagnose_atomic_constraint (t, args, substituted, info);
> >        return cache.save (inst_cache.save (error_mark_node));
> >      }
> >  
> > @@ -2463,7 +2464,7 @@ satisfy_atom (tree t, tree args, sat_info info)
> >      }
> >    result = satisfaction_value (result);
> >    if (result == boolean_false_node && info.diagnose_unsatisfaction_p ())
> > -    diagnose_atomic_constraint (t, args, result, info);
> > +    diagnose_atomic_constraint (t, args, substituted, info);
> >  
> >    return cache.save (inst_cache.save (result));
> >  }
> > @@ -3016,7 +3017,7 @@ get_constraint_error_location (tree t)
> >  /* Diagnose a substitution failure in the atomic constraint T using ARGS.  
> > */
> >  
> >  static void
> > -diagnose_atomic_constraint (tree t, tree args, tree result, sat_info info)
> > +diagnose_atomic_constraint (tree t, tree args, tree substituted, sat_info 
> > info)
> >  {
> >    /* If the constraint is already ill-formed, we've previously diagnosed
> >       the reason.  We should still say why the constraints aren't 
> > satisfied.  */
> > @@ -3047,13 +3048,16 @@ diagnose_atomic_constraint (tree t, tree args, tree 
> > result, sat_info info)
> >        info.in_decl = NULL_TREE;
> >        tsubst_requires_expr (expr, args, info);
> >      }
> > -  else if (!same_type_p (TREE_TYPE (result), boolean_type_node))
> > +  else if (!same_type_p (TREE_TYPE (substituted), boolean_type_node))
> >      error_at (loc, "constraint %qE has type %qT, not %<bool%>",
> > -         t, TREE_TYPE (result));
> > +         t, TREE_TYPE (substituted));
> >    else
> >      {
> >        inform (loc, "the expression %qE evaluated to %<false%>", t);
> > -      maybe_diagnose_standard_trait (loc, expr, args);
> > +      if (TREE_CODE (expr) == TRAIT_EXPR)
> > +   diagnose_trait_expr (loc, expr, args);
> > +      else
> > +   maybe_diagnose_standard_trait (loc, substituted);
> >      }
> >  }
> >  
> > diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> > index a4b62d26459..77f18e84188 100644
> > --- a/gcc/cp/cp-tree.h
> > +++ b/gcc/cp/cp-tree.h
> > @@ -8148,7 +8148,8 @@ extern void finish_static_assert                
> > (tree, tree, location_t,
> >  extern tree finish_decltype_type                (tree, bool, 
> > tsubst_flags_t);
> >  extern tree fold_builtin_is_corresponding_member (location_t, int, tree *);
> >  extern tree fold_builtin_is_pointer_inverconvertible_with_class 
> > (location_t, int, tree *);
> > -extern bool maybe_diagnose_standard_trait  (location_t, tree, tree);
> > +extern void diagnose_trait_expr                    (location_t, tree, 
> > tree);
> > +extern bool maybe_diagnose_standard_trait  (location_t, tree);
> >  extern tree finish_trait_expr                      (location_t, enum 
> > cp_trait_kind, tree, tree);
> >  extern tree finish_trait_type                      (enum cp_trait_kind, 
> > tree, tree, tsubst_flags_t);
> >  extern tree build_lambda_expr                   (void);
> > diff --git a/gcc/cp/method.cc b/gcc/cp/method.cc
> > index de28fc2fd1e..a5eb37ad8c3 100644
> > --- a/gcc/cp/method.cc
> > +++ b/gcc/cp/method.cc
> > @@ -1929,7 +1929,7 @@ is_stub_object (tree expr)
> >  /* Build a std::declval<TYPE>() expression and return it.  */
> >  
> >  static tree
> > -build_trait_object (tree type, tsubst_flags_t complain = tf_none)
> > +build_trait_object (tree type, tsubst_flags_t complain)
> >  {
> >    /* TYPE can't be a function with cv-/ref-qualifiers: std::declval is
> >       defined as
> > @@ -1944,7 +1944,7 @@ build_trait_object (tree type, tsubst_flags_t 
> > complain = tf_none)
> >       || type_memfn_rqual (type) != REF_QUAL_NONE))
> >      {
> >        if (complain & tf_error)
> > -   error ("%qT is a qualified function type", type);
> > +   error ("object cannot have qualified function type %qT", type);
> >        return error_mark_node;
> >      }
> >  
> > @@ -2049,13 +2049,16 @@ build_invoke (tree fn_type, const_tree arg_types, 
> > tsubst_flags_t complain)
> >         }
> >     }
> >  
> > -      tree datum_expr = build_trait_object (datum_type);
> > +      tree datum_expr = build_trait_object (datum_type, complain);
> >        if (!ptrmem_is_same_or_base_of_datum && !datum_is_refwrap)
> >     /* 1.3 & 1.6: Try to dereference datum_expr.  */
> >     datum_expr = build_x_indirect_ref (UNKNOWN_LOCATION, datum_expr,
> >                                        RO_UNARY_STAR, NULL_TREE, complain);
> >  
> > -      tree fn_expr = build_trait_object (fn_type);
> > +      if (error_operand_p (datum_expr))
> > +   return error_mark_node;
> > +
> > +      tree fn_expr = build_trait_object (fn_type, complain);
> >        ptrmem_expr = build_m_component_ref (datum_expr, fn_expr, complain);
> >  
> >        if (error_operand_p (ptrmem_expr))
> > @@ -2072,7 +2075,9 @@ build_invoke (tree fn_type, const_tree arg_types, 
> > tsubst_flags_t complain)
> >    for (int i = is_ptrmemfunc ? 1 : 0; i < TREE_VEC_LENGTH (arg_types); ++i)
> >      {
> >        tree arg_type = TREE_VEC_ELT (arg_types, i);
> > -      tree arg = build_trait_object (arg_type);
> > +      tree arg = build_trait_object (arg_type, complain);
> > +      if (error_operand_p (arg))
> > +   return error_mark_node;
> >        vec_safe_push (args, arg);
> >      }
> >  
> > @@ -2081,8 +2086,8 @@ build_invoke (tree fn_type, const_tree arg_types, 
> > tsubst_flags_t complain)
> >      invoke_expr = build_offset_ref_call_from_tree (ptrmem_expr, &args,
> >                                                complain);
> >    else  /* 1.7.  */
> > -    invoke_expr = finish_call_expr (build_trait_object (fn_type), &args, 
> > false,
> > -                               false, complain);
> > +    invoke_expr = finish_call_expr (build_trait_object (fn_type, complain),
> > +                               &args, false, false, complain);
> >    return invoke_expr;
> >  }
> >  
> > @@ -2504,7 +2509,7 @@ ref_xes_from_temporary (tree to, tree from, bool 
> > direct_init_p)
> >      return false;
> >    /* We don't check is_constructible<T, U>: if T isn't constructible
> >       from U, we won't be able to create a conversion.  */
> > -  tree val = build_trait_object (from);
> > +  tree val = build_trait_object (from, tf_none);
> >    if (val == error_mark_node)
> >      return false;
> >    if (!TYPE_REF_P (from) && TREE_CODE (from) != FUNCTION_TYPE)
> > diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
> > index e133d510716..dd358b30fbb 100644
> > --- a/gcc/cp/semantics.cc
> > +++ b/gcc/cp/semantics.cc
> > @@ -13423,7 +13423,7 @@ trait_expr_value (cp_trait_kind kind, tree type1, 
> > tree type2)
> >  
> >  /* Emit a diagnostic for a failed trait.  */
> >  
> > -static void
> > +void
> >  diagnose_trait_expr (location_t loc, tree expr, tree args)
> >  {
> >    /* Build a "fake" version of the instantiated trait, so we can
> > @@ -13677,23 +13677,17 @@ diagnose_trait_expr (location_t loc, tree expr, 
> > tree args)
> >     more helpful diagnostics.   */
> >  
> >  bool
> > -maybe_diagnose_standard_trait (location_t loc, tree expr, tree args)
> > +maybe_diagnose_standard_trait (location_t loc, tree expr)
> >  {
> > +  gcc_assert (TREE_CODE (expr) != TRAIT_EXPR);
> > +  expr = tree_strip_nop_conversions (expr);
> > +
> >    /* TODO: in some cases it would be possible to provide more helpful
> >       diagnostics for negations of traits, e.g. '!is_same_v<T1, T2>'.  */
> >  
> > -  if (TREE_CODE (expr) == TEMPLATE_ID_EXPR || TREE_CODE (expr) == 
> > SCOPE_REF)
> > -    {
> > -      processing_template_decl_sentinel ptds;
> > -      expr = tsubst_expr (expr, args, tf_none, NULL_TREE);
> > -      expr = tree_strip_nop_conversions (expr);
> > -      args = NULL_TREE;
> > -    }
> > -
> > +  tree args = NULL_TREE;
> >    if (VAR_P (expr) && DECL_LANG_SPECIFIC (expr) && DECL_USE_TEMPLATE 
> > (expr))
> >      {
> > -      gcc_assert (!args);
> > -
> >        tree tinfo = DECL_TEMPLATE_INFO (expr);
> >        if (PRIMARY_TEMPLATE_P (TI_TEMPLATE (tinfo)) && TI_PARTIAL_INFO 
> > (tinfo))
> >     tinfo = TI_PARTIAL_INFO (tinfo);
> > diff --git a/gcc/testsuite/g++.dg/ext/is_invocable5.C 
> > b/gcc/testsuite/g++.dg/ext/is_invocable5.C
> > index 4ef42487ba8..64c5c760389 100644
> > --- a/gcc/testsuite/g++.dg/ext/is_invocable5.C
> > +++ b/gcc/testsuite/g++.dg/ext/is_invocable5.C
> > @@ -13,6 +13,10 @@ static_assert(is_invocable<void(*)(), int>::value, "");  
> > // { dg-error "assert"
> >  // { dg-message "'void \[^'\]*' is not invocable by 'int', because" "" { 
> > target *-*-* } .-1 }
> >  // { dg-error "too many arguments" "" { target *-*-* } .-2 }
> >  
> > +static_assert(is_invocable<void(void*), void() const>::value, "");  // { 
> > dg-error "assert" }
> > +// { dg-message "'void.void..' is not invocable by 'void.. const', 
> > because" "" { target *-*-* } .-1 }
> > +// { dg-error "qualified function type" "" { target *-*-* } .-2 }
> > +
> >  struct A {};
> >  static_assert(is_invocable<const A&&, int, double>::value, "");  // { 
> > dg-error "assert" }
> >  // { dg-message "'const A&&' is not invocable by 'int, double', because" 
> > "" { target *-*-* } .-1 }
> > 

Reply via email to