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.

>      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.

>  {
>    /* 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?

> +
> +  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.

Besides these small comments, LGTM!

> +
> +  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);
> +      else if (DECL_TEMPLATE_SPECIALIZATION (expr))
> +     /* In an explicit specialisation we no longer know what the original
> +        initializer looked like.  */
> +     tinfo = NULL_TREE;
> +
> +      if (tinfo)
> +     {
> +       expr = DECL_INITIAL (DECL_TEMPLATE_RESULT (TI_TEMPLATE (tinfo)));
> +       args = TI_ARGS (tinfo);
> +     }
> +    }
> +
> +  if (TREE_CODE (expr) == TRAIT_EXPR)
> +    {
> +      diagnose_trait_expr (loc, expr, args);
> +      return true;
> +    }
> +
> +  return false;
> +}
> +
>  /* Returns true if TYPE meets the requirements for the specified KIND,
>     false otherwise.
>  
> diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
> index af2cbaff8fd..a077312673e 100644
> --- a/gcc/cp/typeck.cc
> +++ b/gcc/cp/typeck.cc
> @@ -4475,7 +4475,11 @@ cp_build_function_call_vec (tree function, vec<tree, 
> va_gc> **params,
>      {
>        if (complain & tf_error)
>       {
> -       if (!flag_diagnostics_show_caret)
> +       if (is_stub_object (original))
> +         error_at (input_location,
> +                   "%qT cannot be used as a function",
> +                   TREE_TYPE (original));
> +       else if (!flag_diagnostics_show_caret)
>           error_at (input_location,
>                     "%qE cannot be used as a function", original);
>         else if (DECL_P (original))
> @@ -4617,12 +4621,8 @@ convert_arguments (tree typelist, vec<tree, va_gc> 
> **values, tree fndecl,
>        if (type == void_type_node)
>       {
>            if (complain & tf_error)
> -            {
> -           error_args_num (input_location, fndecl, /*too_many_p=*/true);
> -              return i;
> -            }
> -          else
> -            return -1;
> +         error_args_num (input_location, fndecl, /*too_many_p=*/true);
> +       return -1;
>       }
>  
>        /* build_c_cast puts on a NOP_EXPR to make the result not an lvalue.
> diff --git a/gcc/cp/typeck2.cc b/gcc/cp/typeck2.cc
> index 45edd180173..97852d3c727 100644
> --- a/gcc/cp/typeck2.cc
> +++ b/gcc/cp/typeck2.cc
> @@ -119,6 +119,11 @@ cxx_readonly_error (location_t loc, tree arg, enum 
> lvalue_use errstring)
>                         G_("increment of read-only reference %qD"),
>                         G_("decrement of read-only reference %qD"),
>                         TREE_OPERAND (arg, 0));
> +  else if (is_stub_object (arg))
> +    {
> +      gcc_assert (errstring == lv_assign);
> +      error_at (loc, "assignment to read-only type %qT", TREE_TYPE (arg));
> +    }
>    else
>      readonly_error (loc, arg, errstring);
>  }
> diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-traits3.C 
> b/gcc/testsuite/g++.dg/cpp2a/concepts-traits3.C
> index 3e87da4611e..90d859a6c69 100644
> --- a/gcc/testsuite/g++.dg/cpp2a/concepts-traits3.C
> +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-traits3.C
> @@ -1,49 +1,58 @@
>  // PR c++/100474
>  // { dg-do compile { target c++20 } }
>  
> -struct S { S() = delete; S(const S&); };
> +struct S { S() = delete; S(const S&); };  // { dg-line S }
>  
>  template<class T>
>  concept Aggregate = __is_aggregate(T);
> -// { dg-message "'S' is not an aggregate" "" { target *-*-* } .-1  }
> +// { dg-message "'S' is not an aggregate" "" { target *-*-* } S }
>  
>  template<class T>
>  concept TriviallyCopyable = __is_trivially_copyable(T);
> -// { dg-message "'S' is not trivially copyable" "" { target *-*-* } .-1  }
> +// { dg-message "'S' is not trivially copyable" "" { target *-*-* } S }
>  
>  template<class T, class U>
>  concept Assignable = __is_assignable(T, U);
> -// { dg-message "'S' is not assignable from 'int'" "" { target *-*-* } .-1  }
> +// { dg-message "'S' is not assignable from 'int', because" "" { target 
> *-*-* } .-1  }
> +// { dg-error "no match for 'operator='" "" { target *-*-* } .-2 }
>  
>  template<class T, class U>
>  concept TriviallyAssignable = __is_trivially_assignable(T, U);
>  // { dg-message "'S' is not trivially assignable from 'int'" "" { target 
> *-*-* } .-1  }
> +// { dg-error "no match for 'operator='" "" { target *-*-* } .-2 }
>  
>  template<class T, class U>
>  concept NothrowAssignable = __is_nothrow_assignable(T, U);
>  // { dg-message "'S' is not nothrow assignable from 'int'" "" { target *-*-* 
> } .-1  }
> +// { dg-error "no match for 'operator='" "" { target *-*-* } .-2 }
>  
>  template<class T, class... Args>
>  concept Constructible = __is_constructible(T, Args...);
>  // { dg-message "'S' is not default constructible" "" { target *-*-* } .-1  }
> -// { dg-message "'S' is not constructible from 'int'" "" { target *-*-* } 
> .-2  }
> -// { dg-message "'S' is not constructible from 'int, char'" "" { target 
> *-*-* } .-3  }
> +// { dg-error "use of deleted function 'S::S\\(\\)'" "" { target *-*-* } .-2 
> }
> +// { dg-message "'S' is not constructible from 'int'" "" { target *-*-* } 
> .-3  }
> +// { dg-message "'S' is not constructible from 'int, char'" "" { target 
> *-*-* } .-4  }
> +// { dg-error "no matching function for call to 'S::S" "" { target *-*-* } 
> .-5 }
>  
>  template<class T, class... Args>
>  concept TriviallyConstructible = __is_trivially_constructible(T, Args...);
>  // { dg-message "'S' is not trivially default constructible" "" { target 
> *-*-* } .-1  }
> -// { dg-message "'S' is not trivially constructible from 'int'" "" { target 
> *-*-* } .-2  }
> -// { dg-message "'S' is not trivially constructible from 'int, char'" "" { 
> target *-*-* } .-3  }
> +// { dg-error "use of deleted function 'S::S\\(\\)'" "" { target *-*-* } .-2 
> }
> +// { dg-message "'S' is not trivially constructible from 'int'" "" { target 
> *-*-* } .-3  }
> +// { dg-message "'S' is not trivially constructible from 'int, char'" "" { 
> target *-*-* } .-4  }
> +// { dg-error "no matching function for call to 'S::S" "" { target *-*-* } 
> .-5 }
>  
>  template<class T, class... Args>
>  concept NothrowConstructible = __is_nothrow_constructible(T, Args...);
>  // { dg-message "'S' is not nothrow default constructible" "" { target *-*-* 
> } .-1  }
> -// { dg-message "'S' is not nothrow constructible from 'int'" "" { target 
> *-*-* } .-2  }
> -// { dg-message "'S' is not nothrow constructible from 'int, char'" "" { 
> target *-*-* } .-3  }
> +// { dg-error "use of deleted function 'S::S\\(\\)'" "" { target *-*-* } .-2 
> }
> +// { dg-message "'S' is not nothrow constructible from 'int'" "" { target 
> *-*-* } .-3  }
> +// { dg-message "'S' is not nothrow constructible from 'int, char'" "" { 
> target *-*-* } .-4  }
> +// { dg-error "no matching function for call to 'S::S" "" { target *-*-* } 
> .-5 }
>  
>  template<class T>
>  concept UniqueObjReps = __has_unique_object_representations(T);
> -// { dg-message "'S' does not have unique object representations" "" { 
> target *-*-* } .-1  }
> +// { dg-message "'S' does not have unique object representations" "" { 
> target *-*-* } S }
>  
>  static_assert(Aggregate<S>); // { dg-error "assert" }
>  static_assert(TriviallyCopyable<S>); // { dg-error "assert" }
> diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-traits4.C 
> b/gcc/testsuite/g++.dg/cpp2a/concepts-traits4.C
> new file mode 100644
> index 00000000000..caad816bf3d
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-traits4.C
> @@ -0,0 +1,77 @@
> +// PR c++/117294
> +// { dg-do compile { target c++20 } }
> +// { dg-additional-options "-fconcepts-diagnostics-depth=2" }
> +
> +template <typename T> struct norm
> +  { static constexpr bool value = __is_constructible(T); };
> +template <typename T> constexpr bool norm_v = __is_constructible(T);
> +
> +template <typename T> struct part
> +  { static constexpr bool value = __is_constructible(T); };
> +template <typename T> struct part<T*>
> +  { static constexpr bool value = false; };
> +template <typename T> struct part<const T>
> +  { static constexpr bool value = __is_same(T, void); };
> +template <typename T> constexpr bool part_v = __is_constructible(T);
> +template <typename T> constexpr bool part_v<T*> = false;
> +template <typename T> constexpr bool part_v<const T> = __is_same(T, void);
> +
> +template <typename T> struct expl
> +  { static constexpr bool value = __is_constructible(T); };
> +template <> struct expl<int*>
> +  { static constexpr bool value = false; };
> +template <> struct expl<const int>
> +  { static constexpr bool value = __is_same(int, void); };
> +template <typename T> constexpr bool expl_v = __is_constructible(T);
> +template <> constexpr bool expl_v<int*> = false;
> +template <> constexpr bool expl_v<const int> = __is_same(int, void);
> +
> +template <typename T> concept test_norm = norm<T>::value;  // { dg-line norm 
> }
> +template <typename T> concept test_part = part<T>::value;  // { dg-line part 
> }
> +template <typename T> concept test_expl = expl<T>::value;  // { dg-line expl 
> }
> +template <typename T> concept test_norm_v = norm_v<T>;  // { dg-line norm_v }
> +template <typename T> concept test_part_v = part_v<T>;  // { dg-line part_v }
> +template <typename T> concept test_expl_v = expl_v<T>;  // { dg-line expl_v }
> +
> +static_assert(test_norm<void>);  // { dg-error "assert" }
> +static_assert(test_part<void>);  // { dg-error "assert" }
> +static_assert(test_expl<void>);  // { dg-error "assert" }
> +static_assert(test_norm_v<void>);  // { dg-error "assert" }
> +static_assert(test_part_v<void>);  // { dg-error "assert" }
> +static_assert(test_expl_v<void>);  // { dg-error "assert" }
> +// { dg-message "'void' is not default constructible" "" { target *-*-* } 
> norm }
> +// { dg-message "'void' is not default constructible" "" { target *-*-* } 
> part }
> +// { dg-message "'void' is not default constructible" "" { target *-*-* } 
> expl }
> +// { dg-message "'void' is not default constructible" "" { target *-*-* } 
> norm_v }
> +// { dg-message "'void' is not default constructible" "" { target *-*-* } 
> part_v }
> +// { dg-message "'void' is not default constructible" "" { target *-*-* } 
> expl_v }
> +// { dg-prune-output "'void' is incomplete" }
> +
> +static_assert(test_part<int*>);  // { dg-error "assert" }
> +static_assert(test_expl<int*>);  // { dg-error "assert" }
> +static_assert(test_part_v<int*>);  // { dg-error "assert" }
> +static_assert(test_expl_v<int*>);  // { dg-error "assert" }
> +// { dg-message ".with T = int\\*.. evaluated to .false." "" { target *-*-* 
> } part }
> +// { dg-message ".with T = int\\*.. evaluated to .false." "" { target *-*-* 
> } expl }
> +// { dg-message ".with T = int\\*.. evaluated to .false." "" { target *-*-* 
> } part_v }
> +// { dg-message ".with T = int\\*.. evaluated to .false." "" { target *-*-* 
> } expl_v }
> +
> +static_assert(test_part<const int>);  // { dg-error "assert" }
> +static_assert(test_part_v<const int>);  // { dg-error "assert" }
> +// { dg-message "'int' is not the same as 'void'" "" { target *-*-* } part }
> +// { dg-message "'int' is not the same as 'void'" "" { target *-*-* } part_v 
> }
> +
> +struct S { S(int); };
> +static_assert(requires { requires test_norm<S>; });  // { dg-error "assert" }
> +static_assert(requires { requires test_part<S>; });  // { dg-error "assert" }
> +static_assert(requires { requires test_expl<S>; });  // { dg-error "assert" }
> +static_assert(requires { requires test_norm_v<S>; });  // { dg-error 
> "assert" }
> +static_assert(requires { requires test_part_v<S>; });  // { dg-error 
> "assert" }
> +static_assert(requires { requires test_expl_v<S>; });  // { dg-error 
> "assert" }
> +// { dg-message "'S' is not default constructible" "" { target *-*-* } norm }
> +// { dg-message "'S' is not default constructible" "" { target *-*-* } part }
> +// { dg-message "'S' is not default constructible" "" { target *-*-* } expl }
> +// { dg-message "'S' is not default constructible" "" { target *-*-* } 
> norm_v }
> +// { dg-message "'S' is not default constructible" "" { target *-*-* } 
> part_v }
> +// { dg-message "'S' is not default constructible" "" { target *-*-* } 
> expl_v }
> +// { dg-prune-output "no matching function for call" }
> diff --git a/gcc/testsuite/g++.dg/diagnostic/static_assert5.C 
> b/gcc/testsuite/g++.dg/diagnostic/static_assert5.C
> new file mode 100644
> index 00000000000..16681b25e38
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/diagnostic/static_assert5.C
> @@ -0,0 +1,70 @@
> +// PR c++/117294
> +// { dg-do compile { target c++14 } }
> +
> +template <typename T> struct norm
> +  { static constexpr bool value = __is_constructible(T); };
> +template <typename T> constexpr bool norm_v = __is_constructible(T);
> +
> +template <typename T> struct part
> +  { static constexpr bool value = __is_constructible(T); };
> +template <typename T> struct part<T*>
> +  { static constexpr bool value = false; };
> +template <typename T> struct part<const T>
> +  { static constexpr bool value = __is_same(T, void); };
> +template <typename T> constexpr bool part_v = __is_constructible(T);
> +template <typename T> constexpr bool part_v<T*> = false;
> +template <typename T> constexpr bool part_v<const T> = __is_same(T, void);
> +
> +template <typename T> struct expl
> +  { static constexpr bool value = __is_constructible(T); };
> +template <> struct expl<int*>
> +  { static constexpr bool value = false; };
> +template <> struct expl<const int>
> +  { static constexpr bool value = __is_same(int, void); };
> +template <typename T> constexpr bool expl_v = __is_constructible(T);
> +template <> constexpr bool expl_v<int*> = false;
> +template <> constexpr bool expl_v<const int> = __is_same(int, void);
> +
> +// === Primary template can give customised diagnostics when using traits
> +static_assert(norm<void>::value);  // { dg-error "assert" }
> +// { dg-message "'void' is not default constructible" "" { target *-*-* } 
> .-1 }
> +static_assert(part<void>::value);  // { dg-error "assert" }
> +// { dg-message "'void' is not default constructible" "" { target *-*-* } 
> .-1 }
> +static_assert(expl<void>::value);  // { dg-error "assert" }
> +// { dg-message "'void' is not default constructible" "" { target *-*-* } 
> .-1 }
> +static_assert(norm_v<void>);  // { dg-error "assert" }
> +// { dg-message "'void' is not default constructible" "" { target *-*-* } 
> .-1 }
> +static_assert(part_v<void>);  // { dg-error "assert" }
> +// { dg-message "'void' is not default constructible" "" { target *-*-* } 
> .-1 }
> +static_assert(expl_v<void>);  // { dg-error "assert" }
> +// { dg-message "'void' is not default constructible" "" { target *-*-* } 
> .-1 }
> +
> +// { dg-prune-output "'void' is incomplete" }
> +
> +
> +// === Specialisations don't customise just because primary template had 
> trait
> +static_assert(part<int*>::value);  // { dg-error "assert" }
> +// { dg-bogus "default constructible" "" { target *-*-* } .-1 }
> +static_assert(expl<int*>::value);  // { dg-error "assert" }
> +// { dg-bogus "default constructible" "" { target *-*-* } .-1 }
> +static_assert(part_v<int*>);  // { dg-error "assert" }
> +// { dg-bogus "default constructible" "" { target *-*-* } .-1 }
> +static_assert(expl_v<int*>);  // { dg-error "assert" }
> +// { dg-bogus "default constructible" "" { target *-*-* } .-1 }
> +
> +
> +// === But partial specialisations actually using a trait can customise
> +static_assert(part<const int>::value);  // { dg-error "assert" }
> +// { dg-message "'int' is not the same as 'void'" "" { target *-*-* } .-1 }
> +static_assert(part_v<const int>);  // { dg-error "assert" }
> +// { dg-message "'int' is not the same as 'void'" "" { target *-*-* } .-1 }
> +
> +
> +// === For these cases, we no longer know that the error was caused by the 
> trait
> +// === because it's been folded away before we process the failure.
> +static_assert(expl<const int>::value);  // { dg-error "assert" }
> +// { dg-bogus "because" "" { target *-*-* } .-1 }
> +static_assert(expl_v<const int>);  // { dg-error "assert" }
> +// { dg-bogus "because" "" { target *-*-* } .-1 }
> +static_assert(__is_constructible(void));  // { dg-error "assert" }
> +// { dg-bogus "because" "" { target *-*-* } .-1 }
> diff --git a/gcc/testsuite/g++.dg/ext/has_virtual_destructor2.C 
> b/gcc/testsuite/g++.dg/ext/has_virtual_destructor2.C
> new file mode 100644
> index 00000000000..14eea8095d3
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/ext/has_virtual_destructor2.C
> @@ -0,0 +1,27 @@
> +// { dg-do compile { target c++11 } }
> +
> +template <typename T> struct has_virtual_destructor {
> +  static constexpr bool value = __has_virtual_destructor(T);
> +};
> +
> +static_assert(has_virtual_destructor<int>::value, "");  // { dg-error 
> "assert" }
> +// { dg-message "'int' does not have a virtual destructor" "" { target *-*-* 
> } .-1 }
> +
> +struct A {};  // { dg-message "'A' does not have a virtual destructor" }
> +static_assert(has_virtual_destructor<A>::value, "");  // { dg-error "assert" 
> }
> +
> +struct B {
> +  ~B();  // { dg-message "'B' does not have a virtual destructor" }
> +};
> +static_assert(has_virtual_destructor<B>::value, "");  // { dg-error "assert" 
> }
> +
> +struct C {  // { dg-bogus "" }
> +  virtual ~C();  // { dg-bogus "" }
> +};
> +static_assert(has_virtual_destructor<C[5]>::value, "");  // { dg-error 
> "assert" }
> +// { dg-message "'C \\\[5\\\]' does not have a virtual destructor" "" { 
> target *-*-* } .-1 }
> +
> +union U {  // { dg-message "'U' does not have a virtual destructor" }
> +  ~U();
> +};
> +static_assert(has_virtual_destructor<U>::value, "");  // { dg-error "assert" 
> }
> diff --git a/gcc/testsuite/g++.dg/ext/is_assignable2.C 
> b/gcc/testsuite/g++.dg/ext/is_assignable2.C
> new file mode 100644
> index 00000000000..b346d7b235d
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/ext/is_assignable2.C
> @@ -0,0 +1,36 @@
> +// { dg-do compile { target c++11 } }
> +
> +template <typename T>
> +struct is_copy_assignable {
> +  static constexpr bool value = __is_assignable(T&, const T&);
> +};
> +
> +static_assert(is_copy_assignable<const int>::value, "");  // { dg-error 
> "assert" }
> +// { dg-error "assignment to read-only type 'const int'" "" { target *-*-* } 
> .-1 }
> +
> +struct A {
> +  void operator=(A) = delete;  // { dg-message "declared here" }
> +};
> +static_assert(is_copy_assignable<A>::value, "");  // { dg-error "assert" }
> +// { dg-message "is not assignable" "" { target *-*-* } .-1 }
> +// { dg-error "use of deleted function" "" { target *-*-* } .-2 }
> +
> +template <typename T>
> +struct is_nothrow_copy_assignable {
> +  static constexpr bool value = __is_nothrow_assignable(T&, const T&);
> +};
> +struct B {
> +  void operator=(const B&);  // { dg-message "noexcept" }
> +};
> +static_assert(is_nothrow_copy_assignable<B>::value, "");  // { dg-error 
> "assert" }
> +// { dg-message "is not nothrow assignable" "" { target *-*-* } .-1 }
> +
> +template <typename T>
> +struct is_trivially_copy_assignable {
> +  static constexpr bool value = __is_trivially_assignable(T&, const T&);
> +};
> +struct C {
> +  void operator=(const C&);  // { dg-message "non-trivial" }
> +};
> +static_assert(is_trivially_copy_assignable<C>::value, "");  // { dg-error 
> "assert" }
> +// { dg-message "is not trivially assignable" "" { target *-*-* } .-1 }
> diff --git a/gcc/testsuite/g++.dg/ext/is_constructible9.C 
> b/gcc/testsuite/g++.dg/ext/is_constructible9.C
> new file mode 100644
> index 00000000000..5448878c122
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/ext/is_constructible9.C
> @@ -0,0 +1,60 @@
> +// { dg-do compile { target c++11 } }
> +
> +template <typename T, typename... Args>
> +struct is_constructible {
> +  static constexpr bool value = __is_constructible(T, Args...);
> +};
> +
> +static_assert(is_constructible<void>::value, "");  // { dg-error "assert" }
> +// { dg-message "'void' is not default constructible, because" "" { target 
> *-*-* } .-1 }
> +// { dg-error "'void' is incomplete" "" { target *-*-* } .-2 }
> +
> +static_assert(is_constructible<int&, const int&>::value, "");  // { dg-error 
> "assert" }
> +// { dg-message "'int&' is not constructible from 'const int&', because" "" 
> { target *-*-* } .-1 }
> +// { dg-error "discards qualifiers" "" { target *-*-* } .-2 }
> +
> +static_assert(is_constructible<int, int, int>::value, "");  // { dg-error 
> "assert" }
> +// { dg-message "'int' is not constructible from 'int, int', because" "" { 
> target *-*-* } .-1 }
> +// { dg-error "too many initializers for non-class type 'int'" "" { target 
> *-*-* } .-2 }
> +
> +struct A {
> +  A(int);  // { dg-message "candidate" }
> +};
> +static_assert(is_constructible<A, int, int>::value, "");  // { dg-error 
> "assert" }
> +// { dg-message "'A' is not constructible from 'int, int', because" "" { 
> target *-*-* } .-1 }
> +// { dg-error "no matching function for call to" "" { target *-*-* } .-2 }
> +
> +template <typename T, typename... Args>
> +struct is_nothrow_constructible {
> +  static constexpr bool value = __is_nothrow_constructible(T, Args...);
> +};
> +
> +struct B {
> +  B(int);  // { dg-message "candidate" }
> +};
> +static_assert(is_nothrow_constructible<B>::value, "");  // { dg-error 
> "assert" }
> +// { dg-message "'B' is not nothrow default constructible, because" "" { 
> target *-*-* } .-1 }
> +// { dg-error "no matching function for call to" "" { target *-*-* } .-2 }
> +
> +struct C {
> +  C(int);  // { dg-message "noexcept" }
> +};
> +static_assert(is_nothrow_constructible<C, int>::value, "");  // { dg-error 
> "assert" }
> +// { dg-message "'C' is not nothrow constructible from 'int', because" "" { 
> target *-*-* } .-1 }
> +
> +template <typename T, typename... Args>
> +struct is_trivially_constructible {
> +  static constexpr bool value = __is_trivially_constructible(T, Args...);
> +};
> +
> +struct D {
> +  D();  // { dg-message "non-trivial" }
> +};
> +static_assert(is_trivially_constructible<D>::value, "");  // { dg-error 
> "assert" }
> +// { dg-message "'D' is not trivially default constructible, because" "" { 
> target *-*-* } .-1 }
> +
> +struct E {
> +  operator int();  // { dg-message "non-trivial" }
> +};
> +static_assert(is_trivially_constructible<int, E>::value, "");  // { dg-error 
> "assert" }
> +// { dg-message "'int' is not trivially constructible from 'E', because" "" 
> { target *-*-* } .-1 }
> diff --git a/gcc/testsuite/g++.dg/ext/is_convertible7.C 
> b/gcc/testsuite/g++.dg/ext/is_convertible7.C
> new file mode 100644
> index 00000000000..b38fc042de9
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/ext/is_convertible7.C
> @@ -0,0 +1,29 @@
> +// { dg-do compile { target c++11 } }
> +
> +template <typename T, typename U>
> +struct is_convertible {
> +  static constexpr bool value = __is_convertible(T, U);
> +};
> +
> +static_assert(is_convertible<int*, int>::value, "");  // { dg-error "assert" 
> }
> +// { dg-error "invalid conversion" "" { target *-*-* } .-1 }
> +
> +static_assert(is_convertible<int(), double (*)()>::value, "");  // { 
> dg-error "assert" }
> +// { dg-error "invalid conversion" "" { target *-*-* } .-1 }
> +
> +struct A {
> +  explicit A(int);
> +};
> +static_assert(is_convertible<int, A>::value, "");  // { dg-error "assert" }
> +// { dg-error "could not convert 'int' to 'A'" "" { target *-*-* } .-1 }
> +
> +template <typename T, typename U>
> +struct is_nothrow_convertible {
> +  static constexpr bool value = __is_nothrow_convertible(T, U);
> +};
> +
> +struct B {
> +  B(int);  // { dg-message "noexcept" }
> +};
> +static_assert(is_nothrow_convertible<int, B>::value, "");  // { dg-error 
> "assert" }
> +// { dg-message "'int' is not nothrow convertible from 'B', because" "" { 
> target *-*-* } .-1 }
> diff --git a/gcc/testsuite/g++.dg/ext/is_destructible2.C 
> b/gcc/testsuite/g++.dg/ext/is_destructible2.C
> new file mode 100644
> index 00000000000..cb42c3c730c
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/ext/is_destructible2.C
> @@ -0,0 +1,48 @@
> +// { dg-do compile { target c++11 } }
> +
> +template <typename T>
> +struct is_destructible {
> +  static constexpr bool value = __is_destructible(T);
> +};
> +
> +static_assert(is_destructible<void>::value, "");  // { dg-error "assert" }
> +// { dg-message "'void' is not destructible, because" "" { target *-*-* } 
> .-1 }
> +// { dg-error "'void' is incomplete" "" { target *-*-* } .-2 }
> +
> +struct A {
> +  ~A() = delete;  // { dg-message "declared here" }
> +};
> +static_assert(is_destructible<A>::value, "");  // { dg-error "assert" }
> +// { dg-message "'A' is not destructible, because" "" { target *-*-* } .-1 }
> +// { dg-error "use of deleted function" "" { target *-*-* } .-2 }
> +
> +struct B {
> +private:
> +  ~B();  // { dg-message "declared private here" }
> +};
> +static_assert(is_destructible<B>::value, "");  // { dg-error "assert" }
> +// { dg-message "'B' is not destructible, because" "" { target *-*-* } .-1 }
> +// { dg-error "private within this context" "" { target *-*-* } .-2 }
> +
> +template <typename T>
> +struct is_nothrow_destructible {
> +  static constexpr bool value = __is_nothrow_destructible(T);
> +};
> +
> +struct C {
> +  ~C() noexcept(false);  // { dg-message "noexcept" }
> +};
> +static_assert(is_nothrow_destructible<C>::value, "");  // { dg-error 
> "assert" }
> +// { dg-message "'C' is not nothrow destructible, because" "" { target *-*-* 
> } .-1 }
> +
> +template <typename T>
> +struct is_trivially_destructible {
> +  static constexpr bool value = __is_trivially_destructible(T);
> +};
> +
> +struct D {
> +  ~D();
> +};
> +struct E { D d; };  // { dg-message "non-trivial" }
> +static_assert(is_trivially_destructible<E>::value, "");  // { dg-error 
> "assert" }
> +// { dg-message "'E' is not trivially destructible, because" "" { target 
> *-*-* } .-1 }
> diff --git a/gcc/testsuite/g++.dg/ext/is_invocable5.C 
> b/gcc/testsuite/g++.dg/ext/is_invocable5.C
> new file mode 100644
> index 00000000000..4ef42487ba8
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/ext/is_invocable5.C
> @@ -0,0 +1,41 @@
> +// { dg-do compile { target c++11 } }
> +
> +template <typename F, typename... Args>
> +struct is_invocable {
> +  static constexpr bool value = __is_invocable(F, Args...);
> +};
> +
> +static_assert(is_invocable<int>::value, "");  // { dg-error "assert" }
> +// { dg-message "'int' is not invocable, because" "" { target *-*-* } .-1 }
> +// { dg-error "'int' cannot be used as a function" "" { target *-*-* } .-2 }
> +
> +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 }
> +
> +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 }
> +// { dg-error "no match for call to " "" { target *-*-* } .-2 }
> +
> +struct B {
> +  void operator()() = delete;  // { dg-message "declared here" }
> +};
> +static_assert(is_invocable<B>::value, "");  // { dg-error "assert" }
> +// { dg-message "'B' is not invocable, because" "" { target *-*-* } .-1 }
> +// { dg-error "use of deleted function" "" { target *-*-* } .-2 }
> +
> +template <typename F, typename... Args>
> +struct is_nothrow_invocable {
> +  static constexpr bool value = __is_nothrow_invocable(F, Args...);
> +};
> +
> +static_assert(is_nothrow_invocable<void(*)()>::value, "");  // { dg-error 
> "assert" }
> +// { dg-message "'void \[^'\]*' is not nothrow invocable, because" "" { 
> target *-*-* } .-1 }
> +// { dg-message "'void \[^'\]*' is not 'noexcept'" "" { target *-*-* } .-2 }
> +
> +struct C {
> +  int operator()(int, double) const;  // { dg-message "noexcept" }
> +};
> +static_assert(is_nothrow_invocable<const C&, int, int>::value, "");  // { 
> dg-error "assert" }
> +// { dg-message "'const C&' is not nothrow invocable by 'int, int', because" 
> "" { target *-*-* } .-1 }
> diff --git a/gcc/testsuite/g++.dg/ext/is_virtual_base_of_diagnostic2.C 
> b/gcc/testsuite/g++.dg/ext/is_virtual_base_of_diagnostic2.C
> new file mode 100644
> index 00000000000..ac28121d49f
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/ext/is_virtual_base_of_diagnostic2.C
> @@ -0,0 +1,13 @@
> +// { dg-do compile { target c++11 } }
> +
> +template <typename T, typename U>
> +struct is_virtual_base_of {
> +  static constexpr bool value = __builtin_is_virtual_base_of(T, U);
> +};
> +
> +static_assert(is_virtual_base_of<int, int>::value, "");  // { dg-error 
> "assert" }
> +// { dg-message "'int' is not a virtual base of 'int'" "" { target *-*-* } 
> .-1 }
> +
> +struct A {};  // { dg-message "'A' is not a virtual base of 'B'" }
> +struct B : A {};  // { dg-message "declared here" }
> +static_assert(is_virtual_base_of<A, B>::value, "");  // { dg-error "assert" }
> diff --git a/libstdc++-v3/testsuite/20_util/any/misc/any_cast_neg.cc 
> b/libstdc++-v3/testsuite/20_util/any/misc/any_cast_neg.cc
> index 9740e09ac5e..9e5c64cf3f6 100644
> --- a/libstdc++-v3/testsuite/20_util/any/misc/any_cast_neg.cc
> +++ b/libstdc++-v3/testsuite/20_util/any/misc/any_cast_neg.cc
> @@ -27,6 +27,7 @@ void test01()
>    const any y(1);
>    any_cast<int&>(y); // { dg-error "here" }
>    // { dg-error "Template argument must be constructible from a const value" 
> "" { target { *-*-* } } 0 }
> +  // { dg-error "binding reference of type 'int&' to 'const int' discards 
> qualifiers" "" { target { *-*-* } } 0 }
>  }
>  
>  void test02()
> @@ -34,6 +35,7 @@ void test02()
>    any y(1);
>    any_cast<int&&>(y); // { dg-error "here" }
>    // { dg-error "Template argument must be constructible from an lvalue" "" 
> { target { *-*-* } } 0 }
> +  // { dg-error "cannot bind rvalue reference of type 'int&&' to lvalue of 
> type 'int'" "" { target { *-*-* } } 0 }
>  }
>  
>  void test03()
> @@ -41,6 +43,7 @@ void test03()
>    any y(1);
>    any_cast<int&>(std::move(y)); // { dg-error "here" }
>    // { dg-error "Template argument must be constructible from an rvalue" "" 
> { target { *-*-* } } 0 }
> +  // { dg-error "cannot bind non-const lvalue reference of type 'int&' to an 
> rvalue of type 'int'" "" { target { *-*-* } } 0 }
>  }
>  
>  // { dg-prune-output "invalid 'static_cast'" }
> diff --git a/libstdc++-v3/testsuite/20_util/expected/illformed_neg.cc 
> b/libstdc++-v3/testsuite/20_util/expected/illformed_neg.cc
> index 69c13b48a22..69aa4a17960 100644
> --- a/libstdc++-v3/testsuite/20_util/expected/illformed_neg.cc
> +++ b/libstdc++-v3/testsuite/20_util/expected/illformed_neg.cc
> @@ -13,6 +13,7 @@ test_unexpected()
>    std::unexpected<void()> func(test_unexpected); // { dg-error "here" }
>    // { dg-error "no matching function for call to" "" { target *-*-* } 0 }
>    // { dg-error "invalidly declared function type" "" { target *-*-* } 0 }
> +  // { dg-error "could not convert" "" { target *-*-* } 0 }
>  
>    // an array type,
>    std::unexpected<int[2]> array(i); // { dg-error "here" }
> diff --git a/libstdc++-v3/testsuite/20_util/optional/monadic/or_else_neg.cc 
> b/libstdc++-v3/testsuite/20_util/optional/monadic/or_else_neg.cc
> index f5028c15bc3..12a67bbc118 100644
> --- a/libstdc++-v3/testsuite/20_util/optional/monadic/or_else_neg.cc
> +++ b/libstdc++-v3/testsuite/20_util/optional/monadic/or_else_neg.cc
> @@ -26,4 +26,5 @@ test02()
>  
>    std::optional<move_only> mo;
>    mo.or_else([]{ return std::optional<move_only>{}; }); // { dg-error "no 
> matching function" }
> +  // { dg-error "use of deleted function" "" { target *-*-* } 0 }
>  }
> diff --git a/libstdc++-v3/testsuite/23_containers/array/creation/3_neg.cc 
> b/libstdc++-v3/testsuite/23_containers/array/creation/3_neg.cc
> index ae06302b952..133522809b1 100644
> --- a/libstdc++-v3/testsuite/23_containers/array/creation/3_neg.cc
> +++ b/libstdc++-v3/testsuite/23_containers/array/creation/3_neg.cc
> @@ -54,3 +54,5 @@ test03()
>  }
>  
>  // { dg-prune-output "static assertion failed" }
> +// { dg-prune-output "use of deleted function" }
> +// { dg-prune-output "could not convert" }
> diff --git a/libstdc++-v3/testsuite/24_iterators/range_generators/lwg3900.cc 
> b/libstdc++-v3/testsuite/24_iterators/range_generators/lwg3900.cc
> index 957879e8862..08fd5c2ab1d 100644
> --- a/libstdc++-v3/testsuite/24_iterators/range_generators/lwg3900.cc
> +++ b/libstdc++-v3/testsuite/24_iterators/range_generators/lwg3900.cc
> @@ -13,4 +13,5 @@ bar(std::allocator_arg_t, std::pmr::memory_resource& mr) // 
> { dg-error "here" }
>  }
>  
>  // { dg-error "static assertion failed" "" { target *-*-* } 0 }
> -// { dg-error "no matching function .*memory_resource&" "" { target *-*-* } 
> 0 }
> +// { dg-error "could not convert 'const std::pmr::memory_resource'" "" { 
> target *-*-* } 0 }
> +// { dg-error "no matching function \[^\n\r\]*memory_resource&" "" { target 
> *-*-* } 0 }
> diff --git 
> a/libstdc++-v3/testsuite/29_atomics/atomic/requirements/types_neg.cc 
> b/libstdc++-v3/testsuite/29_atomics/atomic/requirements/types_neg.cc
> index 5aa32433c7a..cfe44d255ca 100644
> --- a/libstdc++-v3/testsuite/29_atomics/atomic/requirements/types_neg.cc
> +++ b/libstdc++-v3/testsuite/29_atomics/atomic/requirements/types_neg.cc
> @@ -20,6 +20,7 @@
>  #include <atomic>
>  
>  std::atomic<const int> a; // { dg-error "here" }
> +// { dg-error "assignment to read-only type" "" { target *-*-* } 0 }
>  
>  struct MoveOnly
>  {
> @@ -40,3 +41,4 @@ struct NoMove
>  std::atomic<NoMove> c; // { dg-error "here" }
>  
>  // { dg-error "static assertion failed" "" { target *-*-* } 0 }
> +// { dg-error "use of deleted function" "" { target *-*-* } 0 }
> diff --git 
> a/libstdc++-v3/testsuite/30_threads/stop_token/stop_callback/invocable_neg.cc 
> b/libstdc++-v3/testsuite/30_threads/stop_token/stop_callback/invocable_neg.cc
> index 93b31863650..2b2fce42d2f 100644
> --- 
> a/libstdc++-v3/testsuite/30_threads/stop_token/stop_callback/invocable_neg.cc
> +++ 
> b/libstdc++-v3/testsuite/30_threads/stop_token/stop_callback/invocable_neg.cc
> @@ -32,3 +32,4 @@ test01(std::stop_token& tok, F& f)
>  }
>  
>  // { dg-error "static assertion failed" "" { target *-*-* } 0 }
> +// { dg-error "no match for call" "" { target *-*-* } 0 }
> diff --git a/libstdc++-v3/testsuite/std/format/arguments/args_neg.cc 
> b/libstdc++-v3/testsuite/std/format/arguments/args_neg.cc
> index ded56fe63ab..83c7b22d081 100644
> --- a/libstdc++-v3/testsuite/std/format/arguments/args_neg.cc
> +++ b/libstdc++-v3/testsuite/std/format/arguments/args_neg.cc
> @@ -42,3 +42,4 @@ void test_const_arg()
>  }
>  
>  // { dg-prune-output "no matching function for call to 
> .*::basic_format_arg<" }
> +// { dg-prune-output "use of deleted function" }
> diff --git a/libstdc++-v3/testsuite/std/format/string_neg.cc 
> b/libstdc++-v3/testsuite/std/format/string_neg.cc
> index 09cc9a25b3e..acae88eaf71 100644
> --- a/libstdc++-v3/testsuite/std/format/string_neg.cc
> +++ b/libstdc++-v3/testsuite/std/format/string_neg.cc
> @@ -8,3 +8,5 @@ auto s = std::format(" {9} "); // { dg-error "call to 
> consteval function" }
>  struct X { };
>  std::format_string<X> str(""); // { dg-error "here" }
>  // { dg-error "std::formatter must be specialized" "" { target *-*-* } 0 }
> +
> +// { dg-prune-output "use of deleted function" }
> -- 
> 2.47.0
> 
> 

Reply via email to