On Fri, 30 May 2025, Patrick Palka wrote: > On Fri, 30 May 2025, Nathaniel Shead wrote: > > > On Wed, May 28, 2025 at 02:14:06PM -0400, Patrick Palka wrote: > > > On Tue, 27 May 2025, Nathaniel Shead wrote: > > > > > > > On Wed, Nov 27, 2024 at 11:45:40AM -0500, Patrick Palka wrote: > > > > > On Fri, 8 Nov 2024, Nathaniel Shead wrote: > > > > > > > > > > > Does this approach seem reasonable? I'm pretty sure that the way > > > > > > I've > > > > > > handled the templating here is unideal but I'm not sure what a neat > > > > > > way > > > > > > to do what I'm trying to do here would be; any comments are welcome. > > > > > > > > > > Clever approach, I like it! > > > > > > > > > > > > > > > > > -- >8 -- > > > > > > > > > > > > Currently, concept failures of standard type traits just report > > > > > > 'expression X<T> evaluates to false'. However, many type traits are > > > > > > actually defined in terms of compiler builtins; we can do better > > > > > > here. > > > > > > For instance, 'is_constructible_v' could go on to explain why the > > > > > > type > > > > > > is not constructible, or 'is_invocable_v' could list potential > > > > > > candidates. > > > > > > > > > > That'd be great improvement. > > > > > > > > > > > > > > > > > As a first step to supporting that we need to be able to map the > > > > > > standard type traits to the builtins that they use. Rather than > > > > > > adding > > > > > > another list that would need to be kept up-to-date whenever a > > > > > > builtin is > > > > > > added, this patch instead tries to detect any variable template > > > > > > defined > > > > > > directly in terms of a TRAIT_EXPR. > > > > > > > > > > > > To avoid false positives, we ignore any variable templates that > > > > > > have any > > > > > > specialisations (partial or explicit), even if we wouldn't have > > > > > > chosen > > > > > > that specialisation anyway. This shouldn't affect any of the > > > > > > standard > > > > > > library type traits that I could see. > > > > > > > > > > You should be able to tsubst the TEMPLATE_ID_EXPR directly and look at > > > > > its TI_PARTIAL_INFO in order to determine which (if any) partial > > > > > specialization was selected. And if an explicit specialization was > > > > > selected the resulting VAR_DECL will have DECL_TEMPLATE_SPECIALIZATION > > > > > set. > > > > > > > > > > > ...[snip]... > > > > > > > > > > If we substituted the TEMPLATE_ID_EXPR as a whole we could use the > > > > > DECL_TI_ARGS of that IIUC? > > > > > > > > > > > > > Thanks for your comments, they were very helpful. Here's a totally new > > > > approach which I'm much happier with. I've also removed the "disable in > > > > case any specialisation exists" logic, as on further reflection I don't > > > > imagine this to be the kind of issue I thought it might have been. > > > > > > > > With this patch, > > > > > > > > template <typename T> > > > > constexpr bool is_default_constructible_v = __is_constructible(T); > > > > > > > > template <typename T> > > > > concept default_constructible = is_default_constructible_v<T>; > > > > > > > > static_assert(default_constructible<void>); > > > > > > > > now emits the following error: > > > > > > > > test.cpp:6:15: error: static assertion failed > > > > 6 | static_assert(default_constructible<void>); > > > > | ^~~~~~~~~~~~~~~~~~~~~~~~~~~ > > > > test.cpp:6:15: note: constraints not satisfied > > > > test.cpp:4:9: required by the constraints of ‘template<class T> > > > > concept default_constructible’ > > > > test.cpp:4:33: note: ‘void’ is not default constructible > > > > 4 | concept default_constructible = is_default_constructible_v<T>; > > > > | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ > > > > > > > > There's still a lot of improvements to be made in this area, I think: > > > > > > > > - I haven't yet looked into updating the specific diagnostics emitted by > > > > the traits; I'd like to try to avoid too much code duplication with > > > > the implementation in cp/semantics.cc. (I also don't think the manual > > > > indentation at the start of the message is particularly helpful?) > > > > > > For is_xible / is_convertible etc, perhaps they could use a 'complain' > > > parameter that they propagate through instead of always passing tf_none, > > > similar to build_invoke? Then we can call those predicates directly > > > from diagnose_trait_expr with complain=tf_error so that they elaborate > > > why they failed. > > > > > > > Done; for is_xible I ended up slightly preferring a 'bool explain' > > (since it doesn't really make sense to talk about "complaining" for a > > predicate?) but happy to swap over if that's more consistent. > > > > > Agreed about the extra indentation > > > > > > > > > > > - The message doesn't print the mapping '[with T = void]'; I tried a > > > > couple of things but this doesn't currently look especially > > > > straight-forward, as we don't currently associate the args with the > > > > normalised atomic constraint of the declaration. > > > > > > Maybe we can still print the > > > > > > note: the expression ‘normal<T> [with T = void]’ evaluated to ‘false’ > > > > > > note alongside the extended diagnostics? Which would mean moving the > > > maybe_diagnose_standard_trait call a bit lower in > > > diagnose_atomic_constraint. > > > > > > This would arguably make the diagnostic even noiser, but IMHO the > > > parameter mapping is an important piece of information to omit. > > > > > > > Agreed, can always look at condensing things later but I think this is a > > good improvement. > > > > > > > > > > - Just generally I think there's still a lot of noise in the diagnostic, > > > > and I find the back-to-front ordering of 'required by...' confusing. > > > > > > Agreed in general, but in the case of these type trait diagnostics I > > > think noisiness is kind of inevitable especially if we start explaining > > > in detail why the trait is unsatisified (as e.g. PR117294 requests). > > > > > > > Thanks for your comments! With this patch, the following example: > > > > #include <type_traits> > > #include <concepts> > > struct S { > > S(int); > > }; > > static_assert(std::is_nothrow_constructible_v<S, int>); > > template <std::regular T> void foo(T t); > > int main() { > > foo(S{}); > > } > > > > with '-fdiagnostics-set-output=text:experimental-nesting=yes' now emits: > > > > test.cpp:6:20: error: static assertion failed > > 6 | static_assert(std::is_nothrow_constructible_v<S, int>); > > | ~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ > > • ‘S’ is not nothrow constructible from ‘int’, because > > • ‘S::S(int)’ is not ‘noexcept’ > > test.cpp:4:3: > > 4 | S(int); > > | ^ > > test.cpp: In function ‘int main()’: > > test.cpp:9:6: error: no matching function for call to ‘foo(S)’ > > 9 | foo(S{123}); > > | ~~~^~~~~~~~ > > • there is 1 candidate > > • candidate 1: ‘template<class T> requires regular<T> void foo(T)’ > > test.cpp:7:32: > > 7 | template <std::regular T> void foo(T t); > > | ^~~ > > • template argument deduction/substitution failed: > > • constraints not satisfied > > In file included from test.cpp:2: > > • /home/wreien/.local/include/c++/16.0.0/concepts: In > > substitution of ‘template<class T> requires regular<T> void foo(T) [with > > T = S]’: > > • required from here > > test.cpp:9:6: > > 9 | foo(S{123}); > > | ~~~^~~~~~~~ > > • required for the satisfaction of ‘constructible_from<_Tp>’ > > [with _Tp = S] > > /home/wreien/.local/include/c++/16.0.0/concepts:161:13: > > 161 | concept constructible_from > > | ^~~~~~~~~~~~~~~~~~ > > • required for the satisfaction of ‘default_initializable<_Tp>’ > > [with _Tp = S] > > /home/wreien/.local/include/c++/16.0.0/concepts:166:13: > > 166 | concept default_initializable = > > constructible_from<_Tp> > > | ^~~~~~~~~~~~~~~~~~~~~ > > • required for the satisfaction of ‘semiregular<_Tp>’ [with _Tp = > > S] > > /home/wreien/.local/include/c++/16.0.0/concepts:282:13: > > 282 | concept semiregular = copyable<_Tp> && > > default_initializable<_Tp>; > > | ^~~~~~~~~~~ > > • required for the satisfaction of ‘regular<T>’ [with T = S] > > /home/wreien/.local/include/c++/16.0.0/concepts:356:13: > > 356 | concept regular = semiregular<_Tp> && > > equality_comparable<_Tp>; > > | ^~~~~~~ > > • the expression ‘is_constructible_v<_Tp, _Args ...> [with _Tp = > > S; _Args = {}]’ evaluated to ‘false’ > > /home/wreien/.local/include/c++/16.0.0/concepts:162:30: > > 162 | = destructible<_Tp> && is_constructible_v<_Tp, > > _Args...>; > > | > > ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ > > • ‘S’ is not default constructible, because > > • error: no matching function for call to ‘S::S()’ > > • there are 3 candidates > > • candidate 1: ‘S::S(int)’ > > test.cpp:4:3: > > 4 | S(int); > > | ^ > > • candidate expects 1 argument, 0 provided > > • candidate 2: ‘constexpr S::S(const S&)’ > > test.cpp:3:8: > > 3 | struct S { > > | ^ > > • candidate expects 1 argument, 0 provided > > • candidate 3: ‘constexpr S::S(S&&)’ > > • candidate expects 1 argument, 0 provided > > Awesome, I think users will like this very much! > > > > > Bootstrapped and regtested on x86_64-pc-linux-gnu, OK for trunk? > > > > -- >8 -- > > > > Currently, concept failures of standard type traits just report > > 'expression X<T> evaluates to false'. However, many type traits are > > actually defined in terms of compiler builtins; we can do better here. > > For instance, 'is_constructible_v' could go on to explain why the type > > is not constructible, or 'is_invocable_v' could list potential > > candidates. > > > > Apart from concept diagnostics, this is also useful when using such > > traits in a 'static_assert' directly, so this patch also adjusts the > > diagnostics in that context. > > > > As a first step to supporting that we need to be able to map the > > standard type traits to the builtins that they use. Rather than adding > > another list that would need to be kept up-to-date whenever a builtin is > > added, this patch instead tries to detect any variable template defined > > directly in terms of a TRAIT_EXPR. > > > > This patch also adjusts 'diagnose_trait_expr' to provide more helpful > > diagnostics for these cases. Not all type traits have yet been updated, > > this patch just updates those that seem particularly valuable or > > straight-forward. The function also gets moved to cp/semantics.cc to be > > closer to 'trait_expr_value'. > > > > Various other parts of the compiler are also adjusted here to assist in > > making clear diagnostics, such as making more use of 'is_stub_object' to > > refer to a type directly rather than in terms of 'std::declval<T>()'. > > Additionally, since there are now more cases of nesting within a > > 'static_assert'ion I felt it was helpful for the experimental-nesting > > mode to nest here as well. > > > > PR c++/117294 > > PR c++/113854 > > > > gcc/cp/ChangeLog: > > > > * call.cc (implicit_conversion_error): Hide label when printing > > a stub object. > > (convert_like_internal): Likewise, and nest candidate > > diagnostics. > > * constexpr.cc (diagnose_failing_condition): Nest diagnostics, > > attempt to provide more helpful diagnostics for traits. > > * constraint.cc (diagnose_trait_expr): Move to semantics.cc. > > (diagnose_atomic_constraint): Attempt to provide more helpful > > diagnostics for more traits. > > * cp-tree.h (explain_not_noexcept): Declare new function. > > (is_trivially_xible): Add parameter. > > (is_nothrow_xible): Likewise. > > (is_xible): Likewise. > > (is_convertible): Likewise. > > (is_nothrow_convertible): Likewise. > > (maybe_diagnose_standard_trait): Declare new function. > > * error.cc (dump_type) <case TREE_VEC>: Handle trait types. > > * except.cc (explain_not_noexcept): New function. > > * method.cc (build_trait_object): Add complain parameter. > > (assignable_expr): Add explain parameter to show diagnostics. > > (constructible_expr): Likewise. > > (destructible_expr): Likewise. > > (is_xible_helper): Replace trivial flag with explain flag, > > add diagnostics. > > (is_trivially_xible): New explain flag. > > (is_nothrow_xible): Likewise. > > (is_xible): Likewise. > > (is_convertible_helper): Add complain flag. > > (is_convertible): New explain flag. > > (is_nothrow_convertible): Likewise. > > * semantics.cc (diagnose_trait_expr): Move from constrain.cc. > > Adjust diagnostics for clarity and detail. > > (maybe_diagnose_standard_trait): New function. > > * typeck.cc (cp_build_function_call_vec): Add handling for stub > > objects. > > (convert_arguments): Always return -1 on error. > > * typeck2.cc (cxx_readonly_error): Add handling for stub > > objects. > > > > libstdc++-v3/ChangeLog: > > > > * testsuite/20_util/any/misc/any_cast_neg.cc: Adjust > > diagnostics. > > * testsuite/20_util/expected/illformed_neg.cc: Likewise. > > * testsuite/20_util/optional/monadic/or_else_neg.cc: Likewise. > > * testsuite/23_containers/array/creation/3_neg.cc: Likewise. > > * testsuite/24_iterators/range_generators/lwg3900.cc: Likewise. > > * testsuite/29_atomics/atomic/requirements/types_neg.cc: > > Likewise. > > * testsuite/30_threads/stop_token/stop_callback/invocable_neg.cc: > > Likewise. > > * testsuite/std/format/arguments/args_neg.cc: Likewise. > > * testsuite/std/format/string_neg.cc: Likewise. > > > > gcc/testsuite/ChangeLog: > > > > * g++.dg/cpp2a/concepts-traits3.C: Adjust diagnostics. > > * g++.dg/cpp2a/concepts-traits4.C: New test. > > * g++.dg/diagnostic/static_assert5.C: New test. > > * g++.dg/ext/has_virtual_destructor2.C: New test. > > * g++.dg/ext/is_assignable2.C: New test. > > * g++.dg/ext/is_constructible9.C: New test. > > * g++.dg/ext/is_convertible7.C: New test. > > * g++.dg/ext/is_destructible2.C: New test. > > * g++.dg/ext/is_invocable5.C: New test. > > * g++.dg/ext/is_virtual_base_of_diagnostic2.C: New test. > > > > Signed-off-by: Nathaniel Shead <nathanielosh...@gmail.com> > > Reviewed-by: Patrick Palka <ppa...@redhat.com> > > --- > > gcc/cp/call.cc | 20 +- > > gcc/cp/constexpr.cc | 5 + > > gcc/cp/constraint.cc | 249 +-------------- > > gcc/cp/cp-tree.h | 15 +- > > gcc/cp/error.cc | 14 + > > gcc/cp/except.cc | 12 + > > gcc/cp/method.cc | 194 ++++++++---- > > gcc/cp/semantics.cc | 297 ++++++++++++++++++ > > gcc/cp/typeck.cc | 14 +- > > gcc/cp/typeck2.cc | 5 + > > gcc/testsuite/g++.dg/cpp2a/concepts-traits3.C | 31 +- > > gcc/testsuite/g++.dg/cpp2a/concepts-traits4.C | 77 +++++ > > .../g++.dg/diagnostic/static_assert5.C | 70 +++++ > > .../g++.dg/ext/has_virtual_destructor2.C | 27 ++ > > gcc/testsuite/g++.dg/ext/is_assignable2.C | 36 +++ > > gcc/testsuite/g++.dg/ext/is_constructible9.C | 60 ++++ > > gcc/testsuite/g++.dg/ext/is_convertible7.C | 29 ++ > > gcc/testsuite/g++.dg/ext/is_destructible2.C | 48 +++ > > gcc/testsuite/g++.dg/ext/is_invocable5.C | 41 +++ > > .../ext/is_virtual_base_of_diagnostic2.C | 13 + > > .../20_util/any/misc/any_cast_neg.cc | 3 + > > .../20_util/expected/illformed_neg.cc | 1 + > > .../20_util/optional/monadic/or_else_neg.cc | 1 + > > .../23_containers/array/creation/3_neg.cc | 2 + > > .../24_iterators/range_generators/lwg3900.cc | 3 +- > > .../atomic/requirements/types_neg.cc | 2 + > > .../stop_token/stop_callback/invocable_neg.cc | 1 + > > .../std/format/arguments/args_neg.cc | 1 + > > .../testsuite/std/format/string_neg.cc | 2 + > > 29 files changed, 955 insertions(+), 318 deletions(-) > > create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-traits4.C > > create mode 100644 gcc/testsuite/g++.dg/diagnostic/static_assert5.C > > create mode 100644 gcc/testsuite/g++.dg/ext/has_virtual_destructor2.C > > create mode 100644 gcc/testsuite/g++.dg/ext/is_assignable2.C > > create mode 100644 gcc/testsuite/g++.dg/ext/is_constructible9.C > > create mode 100644 gcc/testsuite/g++.dg/ext/is_convertible7.C > > create mode 100644 gcc/testsuite/g++.dg/ext/is_destructible2.C > > create mode 100644 gcc/testsuite/g++.dg/ext/is_invocable5.C > > create mode 100644 > > gcc/testsuite/g++.dg/ext/is_virtual_base_of_diagnostic2.C > > > > diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc > > index 2c3ef3dfc35..f5b39e403c5 100644 > > --- a/gcc/cp/call.cc > > +++ b/gcc/cp/call.cc > > @@ -4876,6 +4876,11 @@ implicit_conversion_error (location_t loc, tree > > type, tree expr) > > && !CP_AGGREGATE_TYPE_P (type)) > > error_at (loc, "designated initializers cannot be used with a " > > "non-aggregate type %qT", type); > > + else if (is_stub_object (expr)) > > + /* The expression is generated by a trait check, we don't have > > + a useful location to highlight the label. */ > > + error_at (loc, "could not convert %qH to %qI", > > + TREE_TYPE (expr), type); > > else > > { > > range_label_for_type_mismatch label (TREE_TYPE (expr), type); > > @@ -8645,6 +8650,7 @@ convert_like_internal (conversion *convs, tree expr, > > tree fn, int argnum, > > diagnostic_t diag_kind; > > int flags; > > location_t loc = cp_expr_loc_or_input_loc (expr); > > + const bool stub_object_p = is_stub_object (expr); > > > > if (convs->bad_p && !(complain & tf_error)) > > return error_mark_node; > > @@ -8721,7 +8727,10 @@ convert_like_internal (conversion *convs, tree expr, > > tree fn, int argnum, > > "from %qH to %qI", TREE_TYPE (expr), > > totype); > > if (complained) > > - print_z_candidate (loc, N_("candidate is:"), t->cand); > > + { > > + auto_diagnostic_nesting_level sentinel; > > + print_z_candidate (loc, N_("candidate is:"), t->cand); > > + } > > expr = convert_like (t, expr, fn, argnum, > > /*issue_conversion_warnings=*/false, > > /*c_cast_p=*/false, /*nested_p=*/true, > > @@ -8746,7 +8755,14 @@ convert_like_internal (conversion *convs, tree expr, > > tree fn, int argnum, > > else if (t->kind == ck_identity) > > break; > > } > > - if (!complained && expr != error_mark_node) > > + if (!complained && stub_object_p) > > + { > > + /* An error diagnosed within a trait, don't give extra labels. */ > > + error_at (loc, "invalid conversion from %qH to %qI", > > + TREE_TYPE (expr), totype); > > + complained = 1; > > + } > > + else if (!complained && expr != error_mark_node) > > { > > range_label_for_type_mismatch label (TREE_TYPE (expr), totype); > > gcc_rich_location richloc (loc, &label, highlight_colors::percent_h); > > diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc > > index fa754b9a176..95b7f385dbf 100644 > > --- a/gcc/cp/constexpr.cc > > +++ b/gcc/cp/constexpr.cc > > @@ -2122,10 +2122,15 @@ diagnose_failing_condition (tree bad, location_t > > cloc, bool show_expr_p, > > if (TREE_CODE (bad) == CLEANUP_POINT_EXPR) > > bad = TREE_OPERAND (bad, 0); > > > > + auto_diagnostic_nesting_level sentinel; > > + > > /* Actually explain the failure if this is a concept check or a > > requires-expression. */ > > if (concept_check_p (bad) || TREE_CODE (bad) == REQUIRES_EXPR) > > diagnose_constraints (cloc, bad, NULL_TREE); > > + /* Similarly if this is a standard trait. */ > > + else if (maybe_diagnose_standard_trait (cloc, bad, NULL_TREE)) > > + ; > > else if (COMPARISON_CLASS_P (bad) > > && ARITHMETIC_TYPE_P (TREE_TYPE (TREE_OPERAND (bad, 0)))) > > { > > diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc > > index 90625707043..bb32c8ab111 100644 > > --- a/gcc/cp/constraint.cc > > +++ b/gcc/cp/constraint.cc > > @@ -3013,233 +3013,6 @@ get_constraint_error_location (tree t) > > return input_location; > > } > > > > -/* Emit a diagnostic for a failed trait. */ > > - > > -static void > > -diagnose_trait_expr (tree expr, tree args) > > -{ > > - location_t loc = cp_expr_location (expr); > > - > > - /* Build a "fake" version of the instantiated trait, so we can > > - get the instantiated types from result. */ > > - ++processing_template_decl; > > - expr = tsubst_expr (expr, args, tf_none, NULL_TREE); > > - --processing_template_decl; > > - > > - tree t1 = TRAIT_EXPR_TYPE1 (expr); > > - tree t2 = TRAIT_EXPR_TYPE2 (expr); > > - if (t2 && TREE_CODE (t2) == TREE_VEC) > > - { > > - /* Convert the TREE_VEC of arguments into a TREE_LIST, since we can't > > - directly print a TREE_VEC but we can a TREE_LIST via the E format > > - specifier. */ > > - tree list = NULL_TREE; > > - for (tree t : tree_vec_range (t2)) > > - list = tree_cons (NULL_TREE, t, list); > > - t2 = nreverse (list); > > - } > > - switch (TRAIT_EXPR_KIND (expr)) > > - { > > - case CPTK_HAS_NOTHROW_ASSIGN: > > - inform (loc, " %qT is not nothrow copy assignable", t1); > > - break; > > - case CPTK_HAS_NOTHROW_CONSTRUCTOR: > > - inform (loc, " %qT is not nothrow default constructible", t1); > > - break; > > - case CPTK_HAS_NOTHROW_COPY: > > - inform (loc, " %qT is not nothrow copy constructible", t1); > > - break; > > - case CPTK_HAS_TRIVIAL_ASSIGN: > > - inform (loc, " %qT is not trivially copy assignable", t1); > > - break; > > - case CPTK_HAS_TRIVIAL_CONSTRUCTOR: > > - inform (loc, " %qT is not trivially default constructible", t1); > > - break; > > - case CPTK_HAS_TRIVIAL_COPY: > > - inform (loc, " %qT is not trivially copy constructible", t1); > > - break; > > - case CPTK_HAS_TRIVIAL_DESTRUCTOR: > > - inform (loc, " %qT is not trivially destructible", t1); > > - break; > > - case CPTK_HAS_UNIQUE_OBJ_REPRESENTATIONS: > > - inform (loc, " %qT does not have unique object representations", > > t1); > > - break; > > - case CPTK_HAS_VIRTUAL_DESTRUCTOR: > > - inform (loc, " %qT does not have a virtual destructor", t1); > > - break; > > - case CPTK_IS_ABSTRACT: > > - inform (loc, " %qT is not an abstract class", t1); > > - break; > > - case CPTK_IS_AGGREGATE: > > - inform (loc, " %qT is not an aggregate", t1); > > - break; > > - case CPTK_IS_ARRAY: > > - inform (loc, " %qT is not an array", t1); > > - break; > > - case CPTK_IS_ASSIGNABLE: > > - inform (loc, " %qT is not assignable from %qT", t1, t2); > > - break; > > - case CPTK_IS_BASE_OF: > > - inform (loc, " %qT is not a base of %qT", t1, t2); > > - break; > > - case CPTK_IS_BOUNDED_ARRAY: > > - inform (loc, " %qT is not a bounded array", t1); > > - break; > > - case CPTK_IS_CLASS: > > - inform (loc, " %qT is not a class", t1); > > - break; > > - case CPTK_IS_CONST: > > - inform (loc, " %qT is not a const type", t1); > > - break; > > - case CPTK_IS_CONSTRUCTIBLE: > > - if (!t2) > > - inform (loc, " %qT is not default constructible", t1); > > - else > > - inform (loc, " %qT is not constructible from %qE", t1, t2); > > - break; > > - case CPTK_IS_CONVERTIBLE: > > - inform (loc, " %qT is not convertible from %qE", t2, t1); > > - break; > > - case CPTK_IS_DESTRUCTIBLE: > > - inform (loc, " %qT is not destructible", t1); > > - break; > > - case CPTK_IS_EMPTY: > > - inform (loc, " %qT is not an empty class", t1); > > - break; > > - case CPTK_IS_ENUM: > > - inform (loc, " %qT is not an enum", t1); > > - break; > > - case CPTK_IS_FINAL: > > - inform (loc, " %qT is not a final class", t1); > > - break; > > - case CPTK_IS_FUNCTION: > > - inform (loc, " %qT is not a function", t1); > > - break; > > - case CPTK_IS_INVOCABLE: > > - if (!t2) > > - inform (loc, " %qT is not invocable", t1); > > - else > > - inform (loc, " %qT is not invocable by %qE", t1, t2); > > - break; > > - case CPTK_IS_LAYOUT_COMPATIBLE: > > - inform (loc, " %qT is not layout compatible with %qT", t1, t2); > > - break; > > - case CPTK_IS_LITERAL_TYPE: > > - inform (loc, " %qT is not a literal type", t1); > > - break; > > - case CPTK_IS_MEMBER_FUNCTION_POINTER: > > - inform (loc, " %qT is not a member function pointer", t1); > > - break; > > - case CPTK_IS_MEMBER_OBJECT_POINTER: > > - inform (loc, " %qT is not a member object pointer", t1); > > - break; > > - case CPTK_IS_MEMBER_POINTER: > > - inform (loc, " %qT is not a member pointer", t1); > > - break; > > - case CPTK_IS_NOTHROW_ASSIGNABLE: > > - inform (loc, " %qT is not nothrow assignable from %qT", t1, t2); > > - break; > > - case CPTK_IS_NOTHROW_CONSTRUCTIBLE: > > - if (!t2) > > - inform (loc, " %qT is not nothrow default constructible", t1); > > - else > > - inform (loc, " %qT is not nothrow constructible from %qE", t1, t2); > > - break; > > - case CPTK_IS_NOTHROW_CONVERTIBLE: > > - inform (loc, " %qT is not nothrow convertible from %qE", t2, t1); > > - break; > > - case CPTK_IS_NOTHROW_DESTRUCTIBLE: > > - inform (loc, " %qT is not nothrow destructible", t1); > > - break; > > - case CPTK_IS_NOTHROW_INVOCABLE: > > - if (!t2) > > - inform (loc, " %qT is not nothrow invocable", t1); > > - else > > - inform (loc, " %qT is not nothrow invocable by %qE", t1, t2); > > - break; > > - case CPTK_IS_OBJECT: > > - inform (loc, " %qT is not an object type", t1); > > - break; > > - case CPTK_IS_POINTER_INTERCONVERTIBLE_BASE_OF: > > - inform (loc, " %qT is not pointer-interconvertible base of %qT", > > - t1, t2); > > - break; > > - case CPTK_IS_POD: > > - inform (loc, " %qT is not a POD type", t1); > > - break; > > - case CPTK_IS_POINTER: > > - inform (loc, " %qT is not a pointer", t1); > > - break; > > - case CPTK_IS_POLYMORPHIC: > > - inform (loc, " %qT is not a polymorphic type", t1); > > - break; > > - case CPTK_IS_REFERENCE: > > - inform (loc, " %qT is not a reference", t1); > > - break; > > - case CPTK_IS_SAME: > > - inform (loc, " %qT is not the same as %qT", t1, t2); > > - break; > > - case CPTK_IS_SCOPED_ENUM: > > - inform (loc, " %qT is not a scoped enum", t1); > > - break; > > - case CPTK_IS_STD_LAYOUT: > > - inform (loc, " %qT is not an standard layout type", t1); > > - break; > > - case CPTK_IS_TRIVIAL: > > - inform (loc, " %qT is not a trivial type", t1); > > - break; > > - case CPTK_IS_TRIVIALLY_ASSIGNABLE: > > - inform (loc, " %qT is not trivially assignable from %qT", t1, t2); > > - break; > > - case CPTK_IS_TRIVIALLY_CONSTRUCTIBLE: > > - if (!t2) > > - inform (loc, " %qT is not trivially default constructible", t1); > > - else > > - inform (loc, " %qT is not trivially constructible from %qE", t1, t2); > > - break; > > - case CPTK_IS_TRIVIALLY_COPYABLE: > > - inform (loc, " %qT is not trivially copyable", t1); > > - break; > > - case CPTK_IS_TRIVIALLY_DESTRUCTIBLE: > > - inform (loc, " %qT is not trivially destructible", t1); > > - break; > > - case CPTK_IS_UNBOUNDED_ARRAY: > > - inform (loc, " %qT is not an unbounded array", t1); > > - break; > > - case CPTK_IS_UNION: > > - inform (loc, " %qT is not a union", t1); > > - break; > > - case CPTK_IS_VIRTUAL_BASE_OF: > > - inform (loc, " %qT is not a virtual base of %qT", t1, t2); > > - break; > > - case CPTK_IS_VOLATILE: > > - inform (loc, " %qT is not a volatile type", t1); > > - break; > > - case CPTK_RANK: > > - inform (loc, " %qT cannot yield a rank", t1); > > - break; > > - case CPTK_REF_CONSTRUCTS_FROM_TEMPORARY: > > - inform (loc, " %qT is not a reference that binds to a temporary " > > - "object of type %qT (direct-initialization)", t1, t2); > > - break; > > - case CPTK_REF_CONVERTS_FROM_TEMPORARY: > > - inform (loc, " %qT is not a reference that binds to a temporary " > > - "object of type %qT (copy-initialization)", t1, t2); > > - break; > > - case CPTK_IS_DEDUCIBLE: > > - inform (loc, " %qD is not deducible from %qT", t1, t2); > > - break; > > -#define DEFTRAIT_TYPE(CODE, NAME, ARITY) \ > > - case CPTK_##CODE: > > -#include "cp-trait.def" > > -#undef DEFTRAIT_TYPE > > - /* Type-yielding traits aren't expressions. */ > > - gcc_unreachable (); > > - /* We deliberately omit the default case so that when adding a new > > - trait we'll get reminded (by way of a warning) to handle it here. > > */ > > - } > > -} > > - > > /* Diagnose a substitution failure in the atomic constraint T using ARGS. > > */ > > > > static void > > @@ -3264,25 +3037,23 @@ diagnose_atomic_constraint (tree t, tree args, tree > > result, sat_info info) > > /* Generate better diagnostics for certain kinds of expressions. */ > > tree expr = ATOMIC_CONSTR_EXPR (t); > > STRIP_ANY_LOCATION_WRAPPER (expr); > > - switch (TREE_CODE (expr)) > > + > > + if (TREE_CODE (expr) == REQUIRES_EXPR) > > { > > - case TRAIT_EXPR: > > - diagnose_trait_expr (expr, args); > > - break; > > - case REQUIRES_EXPR: > > gcc_checking_assert (info.diagnose_unsatisfaction_p ()); > > /* Clear in_decl before replaying the substitution to avoid emitting > > seemingly unhelpful "in declaration ..." notes that follow some > > substitution failure error messages. */ > > info.in_decl = NULL_TREE; > > tsubst_requires_expr (expr, args, info); > > - break; > > - default: > > - if (!same_type_p (TREE_TYPE (result), boolean_type_node)) > > - error_at (loc, "constraint %qE has type %qT, not %<bool%>", > > - t, TREE_TYPE (result)); > > - else > > - inform (loc, "the expression %qE evaluated to %<false%>", t); > > + } > > + else if (!same_type_p (TREE_TYPE (result), boolean_type_node)) > > + error_at (loc, "constraint %qE has type %qT, not %<bool%>", > > + t, TREE_TYPE (result)); > > + else > > + { > > + inform (loc, "the expression %qE evaluated to %<false%>", t); > > + maybe_diagnose_standard_trait (loc, expr, args); > > } > > } > > > > diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h > > index 19c0b452d86..89e0d96809e 100644 > > --- a/gcc/cp/cp-tree.h > > +++ b/gcc/cp/cp-tree.h > > @@ -7387,6 +7387,7 @@ extern int nothrow_libfn_p > > (const_tree); > > extern void check_handlers (tree); > > extern tree finish_noexcept_expr (tree, tsubst_flags_t); > > extern bool expr_noexcept_p (tree, tsubst_flags_t); > > +extern void explain_not_noexcept (tree); > > extern void perform_deferred_noexcept_checks (void); > > extern bool nothrow_spec_p (const_tree); > > extern bool type_noexcept_p (const_tree); > > @@ -7508,11 +7509,14 @@ extern void finish_thunk (tree); > > extern void use_thunk (tree, bool); > > extern bool trivial_fn_p (tree); > > extern tree forward_parm (tree); > > -extern bool is_trivially_xible (enum tree_code, tree, > > tree); > > -extern bool is_nothrow_xible (enum tree_code, tree, > > tree); > > -extern bool is_xible (enum tree_code, tree, > > tree); > > -extern bool is_convertible (tree, tree); > > -extern bool is_nothrow_convertible (tree, tree); > > +extern bool is_trivially_xible (enum tree_code, tree, > > tree, > > + bool = false); > > +extern bool is_nothrow_xible (enum tree_code, tree, > > tree, > > + bool = false); > > +extern bool is_xible (enum tree_code, tree, > > tree, > > + bool = false); > > +extern bool is_convertible (tree, tree, bool = false); > > +extern bool is_nothrow_convertible (tree, tree, bool = false); > > extern bool ref_xes_from_temporary (tree, tree, bool); > > extern tree get_defaulted_eh_spec (tree, tsubst_flags_t = > > tf_warning_or_error); > > extern bool maybe_explain_implicit_delete (tree); > > @@ -8140,6 +8144,7 @@ extern void finish_static_assert > > (tree, tree, location_t, > > extern tree finish_decltype_type (tree, bool, > > tsubst_flags_t); > > extern tree fold_builtin_is_corresponding_member (location_t, int, tree *); > > extern tree fold_builtin_is_pointer_inverconvertible_with_class > > (location_t, int, tree *); > > +extern bool maybe_diagnose_standard_trait (location_t, tree, tree); > > extern tree finish_trait_expr (location_t, enum > > cp_trait_kind, tree, tree); > > extern tree finish_trait_type (enum cp_trait_kind, > > tree, tree, tsubst_flags_t); > > extern tree build_lambda_expr (void); > > diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc > > index d52dad3db29..9cf04fa4191 100644 > > --- a/gcc/cp/error.cc > > +++ b/gcc/cp/error.cc > > @@ -704,6 +704,20 @@ dump_type (cxx_pretty_printer *pp, tree t, int flags) > > } > > break; > > > > + case TREE_VEC: > > + { > > + /* A list of types used for a trait. */ > > + bool need_comma = false; > > + for (tree arg : tree_vec_range (t)) > > + { > > + if (need_comma) > > + pp_separate_with_comma (pp); > > + dump_type (pp, arg, flags); > > + need_comma = true; > > + } > > + } > > + break; > > + > > Is this needed because of the removed TREE_VEC -> TREE_LIST conversion > in diagnose_trait_expr? I think I'd prefer keep performing that > conversion there, making sure to pass a TREE_LIST version of 't2' to > inform, and leave open the question of how the pretty printer should > handle TREE_VEC. > > > 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.
Maybe we could pass the non-constant-evaluated 'result' from satisfy_atom to diagnose_atomic_constraint, that way it doesn't have to re-do the substitution at all. > > 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 > > > >