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

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;
+
     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)
 {
   /* 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*/)
 {
-  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;
+
+  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;
+    }
+
+  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