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.

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.

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

> 
> Depending on how much time I find myself with I might take a look at
> some of these further issues later, but in the meantime, does this look
> like an improvement over the status quo?
> 
> 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.
> 
> 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.
> 
> The new diagnostics from this patch are not immediately much better;
> however, it would be relatively straight-forward to update the messages
> in 'diagnose_trait_expr' to provide these new details, which can be done
> in a future patch.
> 
> 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.
> 
>       PR c++/117294
>       PR c++/113854
> 
> gcc/cp/ChangeLog:
> 
>       * cp-tree.h (maybe_diagnose_standard_trait): Declare.
>       * constexpr.cc (diagnose_failing_condition): Call it.
>       * constraint.cc (diagnose_trait_expr): Add location parameter.
>       (maybe_diagnose_standard_trait): New function.
>       (diagnose_atomic_constraint): Call it.
> 
> gcc/testsuite/ChangeLog:
> 
>       * g++.dg/cpp2a/concepts-traits4.C: New test.
>       * g++.dg/diagnostic/static_assert5.C: New test.
> 
> Signed-off-by: Nathaniel Shead <nathanielosh...@gmail.com>
> Reviewed-by: Patrick Palka <ppa...@redhat.com>
> ---
>  gcc/cp/constexpr.cc                           |  2 +
>  gcc/cp/constraint.cc                          | 49 +++++++++++++++++--
>  gcc/cp/cp-tree.h                              |  1 +
>  gcc/testsuite/g++.dg/cpp2a/concepts-traits4.C | 40 +++++++++++++++
>  .../g++.dg/diagnostic/static_assert5.C        | 26 ++++++++++
>  5 files changed, 114 insertions(+), 4 deletions(-)
>  create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-traits4.C
>  create mode 100644 gcc/testsuite/g++.dg/diagnostic/static_assert5.C
> 
> diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
> index fa754b9a176..0c8a9199a67 100644
> --- a/gcc/cp/constexpr.cc
> +++ b/gcc/cp/constexpr.cc
> @@ -2134,6 +2134,8 @@ diagnose_failing_condition (tree bad, location_t cloc, 
> bool show_expr_p,
>        tree cond = build2 (TREE_CODE (bad), boolean_type_node, op0, op1);
>        inform (cloc, "the comparison reduces to %qE", cond);
>      }
> +  else if (maybe_diagnose_standard_trait (cloc, bad, NULL_TREE))
> +    ;
>    else if (show_expr_p)
>      inform (cloc, "%qE evaluates to false", bad);
>  }
> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> index 44fb086c630..f21d61be94b 100644
> --- a/gcc/cp/constraint.cc
> +++ b/gcc/cp/constraint.cc
> @@ -3016,10 +3016,8 @@ get_constraint_error_location (tree t)
>  /* Emit a diagnostic for a failed trait.  */
>  
>  static void
> -diagnose_trait_expr (tree expr, tree args)
> +diagnose_trait_expr (location_t loc, 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;
> @@ -3231,6 +3229,46 @@ diagnose_trait_expr (tree expr, tree args)
>      }
>  }
>  
> +/* 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)
> +{
> +  if (TREE_CODE (expr) == TEMPLATE_ID_EXPR)
> +    {
> +      processing_template_decl_sentinel ptds;
> +      expr = tsubst_expr (expr, args, tf_none, NULL_TREE);
> +      expr = tree_strip_nop_conversions (expr);
> +      if (TREE_CODE (expr) == CLEANUP_POINT_EXPR)
> +     expr = TREE_OPERAND (expr, 0);

What's this CLEANUP_POINT_EXPR handling for?  I'd be surprised to see a
CLEANUP_POINT_EXPR after instantiating a suitable variable template-id.

> +      args = NULL_TREE;
> +    }
> +
> +  /* Ensure we haven't picked a specialization.  This will mean we won't
> +     have the nice error if the trait was only used in the specialization,
> +     but that is not how the standard library will use these traits.  */
> +  if (VAR_P (expr)
> +      && DECL_LANG_SPECIFIC (expr)
> +      && DECL_USE_TEMPLATE (expr)
> +      && !DECL_TEMPLATE_SPECIALIZATION (expr)
> +      && PRIMARY_TEMPLATE_P (DECL_TI_TEMPLATE (expr))
> +      && !TI_PARTIAL_INFO (DECL_TEMPLATE_INFO (expr)))

Rather than giving up when a partial specialization is selected, I think
we can leverage the information in TI_PARTIAL_INFO to give a better
diagnostic in that case too (if the partial specialization is suitably
defined):

  template <typename T> constexpr bool partial_spec = false;
  template <typename T> constexpr bool partial_spec<T*> = __is_constructible(T);

  static_assert(partial_spec<int*>);

We just need to use TI_TEMPLATE/ARGS (TI_PARTIAL_INFO (DECL_TEMPLATE_INFO 
(expr))
instead of DECL_TI_TEMPLATE/ARGS in that case.

(For an explicit specialization, I think we have no choice but to give
up because we don't know what the original initializer looked like.)

> +    {
> +      gcc_assert (!args);
> +      tree result = DECL_TEMPLATE_RESULT (DECL_TI_TEMPLATE (expr));
> +      tree initial = DECL_INITIAL (result);
> +      if (TREE_CODE (initial) == TRAIT_EXPR)
> +     {
> +       diagnose_trait_expr (loc, initial, DECL_TI_ARGS (expr));
> +       return true;
> +     }
> +    }
> +
> +  return false;
> +}
> +
>  /* Diagnose a substitution failure in the atomic constraint T using ARGS.  */
>  
>  static void
> @@ -3255,10 +3293,13 @@ 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);
> +  if (maybe_diagnose_standard_trait (loc, expr, args))
> +    return;
> +
>    switch (TREE_CODE (expr))
>      {
>      case TRAIT_EXPR:
> -      diagnose_trait_expr (expr, args);
> +      diagnose_trait_expr (loc, expr, args);
>        break;
>      case REQUIRES_EXPR:
>        gcc_checking_assert (info.diagnose_unsatisfaction_p ());
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index 175ab287490..e989a6302d7 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -8793,6 +8793,7 @@ extern bool atomic_constraints_identical_p      (tree, 
> tree);
>  extern hashval_t iterative_hash_constraint      (tree, hashval_t);
>  extern hashval_t hash_atomic_constraint         (tree);
>  extern void diagnose_constraints                (location_t, tree, tree);
> +extern bool maybe_diagnose_standard_trait    (location_t, tree, tree);
>  
>  extern void note_failed_type_completion_for_satisfaction (tree);
>  
> 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..4d5f1774f46
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-traits4.C
> @@ -0,0 +1,40 @@
> +// PR c++/117294
> +// { dg-do compile { target c++20 } }
> +// { dg-additional-options "-fconcepts-diagnostics-depth=2" }
> +
> +template <typename T> constexpr bool normal = __is_constructible(T);
> +
> +template <typename T> constexpr bool partial_spec = __is_constructible(T);
> +template <typename T> constexpr bool partial_spec<T*> = false;
> +
> +template <typename T> constexpr bool explicit_spec = __is_constructible(T);
> +template <> constexpr bool explicit_spec<int> = false;
> +
> +template <typename T>
> +concept test_normal = normal<T>;  // { dg-line normal }
> +
> +template <typename T>
> +concept test_partial_spec = partial_spec<T>;  // { dg-line partial }
> +
> +template <typename T>
> +concept test_explicit_spec = explicit_spec<T>;  // { dg-line explicit }
> +
> +static_assert(test_normal<void>);  // { dg-error "assert" }
> +static_assert(test_partial_spec<void>);  // { dg-error "assert" }
> +static_assert(test_explicit_spec<void>);  // { dg-error "assert" }
> +// { dg-message "'void' is not default constructible" "" { target *-*-* } 
> normal }
> +// { dg-message "'void' is not default constructible" "" { target *-*-* } 
> partial }
> +// { dg-message "'void' is not default constructible" "" { target *-*-* } 
> explicit }
> +
> +static_assert(test_partial_spec<int*>);  // { dg-error "assert" }
> +static_assert(test_explicit_spec<int>);  // { dg-error "assert" }
> +// { dg-message ".with T = int\\*.. evaluated to .false." "" { target *-*-* 
> } partial }
> +// { dg-message ".with T = int.. evaluated to .false." "" { target *-*-* } 
> explicit }
> +
> +struct S { S(int); };
> +static_assert(requires { requires test_normal<S>; });  // { dg-error 
> "assert" }
> +static_assert(requires { requires test_partial_spec<S>; });  // { dg-error 
> "assert" }
> +static_assert(requires { requires test_explicit_spec<S>; });  // { dg-error 
> "assert" }
> +// { dg-message "'S' is not default constructible" "" { target *-*-* } 
> normal }
> +// { dg-message "'S' is not default constructible" "" { target *-*-* } 
> partial }
> +// { dg-message "'S' is not default constructible" "" { target *-*-* } 
> explicit }
> 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..e5a0684d01a
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/diagnostic/static_assert5.C
> @@ -0,0 +1,26 @@
> +// PR c++/117294
> +// { dg-do compile { target c++14 } }
> +
> +template <typename T> constexpr bool normal = __is_constructible(T);
> +
> +template <typename T> constexpr bool partial_spec = __is_constructible(T);
> +template <typename T> constexpr bool partial_spec<T*> = false;
> +
> +template <typename T> constexpr bool explicit_spec = __is_constructible(T);
> +template <> constexpr bool explicit_spec<int> = false;
> +
> +static_assert(normal<void>);  // { dg-error "assert" }
> +// { dg-message "'void' is not default constructible" "" { target *-*-* } 
> .-1 }
> +static_assert(partial_spec<void>);  // { dg-error "assert" }
> +// { dg-message "'void' is not default constructible" "" { target *-*-* } 
> .-1 }
> +static_assert(explicit_spec<void>);  // { dg-error "assert" }
> +// { dg-message "'void' is not default constructible" "" { target *-*-* } 
> .-1 }
> +
> +static_assert(partial_spec<int*>);  // { dg-error "assert" }
> +// { dg-bogus "default constructible" "" { target *-*-* } .-1 }
> +static_assert(explicit_spec<int>);  // { dg-error "assert" }
> +// { dg-bogus "default constructible" "" { target *-*-* } .-1 }
> +
> +static_assert(__is_constructible(void));  // { dg-error "assert" }
> +// The above should not ICE, but we no longer know that the error was caused
> +// by the trait because it's been folded before we process the failure.
> -- 
> 2.47.0
> 
> 

Reply via email to