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.

> 
> 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.
> 
> This logic could also perhaps be used by 'diagnose_failing_condition' so
> that cases like 'static_assert(std::is_constructible_v<T>)' get the same
> treatment; this patch doesn't attempt to update this yet.
> 
>       PR c++/117294
>       PR c++/113854
> 
> gcc/cp/ChangeLog:
> 
>       * constraint.cc (diagnose_trait_expr): Take location to diagnose
>       at explicitly.
>       (maybe_unwrap_standard_trait): New function.
>       (diagnose_atomic_constraint): Use it; pass in the location of
>       the atomic constraint to diagnose_trait_expr.
> 
> gcc/testsuite/ChangeLog:
> 
>       * g++.dg/cpp2a/concepts-traits4.C: New test.
> 
> Signed-off-by: Nathaniel Shead <nathanielosh...@gmail.com>
> ---
>  gcc/cp/constraint.cc                          | 52 +++++++++++++++++--
>  gcc/testsuite/g++.dg/cpp2a/concepts-traits4.C | 31 +++++++++++
>  2 files changed, 79 insertions(+), 4 deletions(-)
>  create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-traits4.C
> 
> diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> index 8a36b9c88c4..c683e6a44dd 100644
> --- a/gcc/cp/constraint.cc
> +++ b/gcc/cp/constraint.cc
> @@ -3108,10 +3108,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;
> @@ -3323,6 +3321,51 @@ 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
> +   slightly more helpful diagnostics.
> +
> +   However, don't unwrap if the type has been specialized (even if we
> +   wouldn't have used said specialization)..  */
> +
> +static void
> +maybe_unwrap_standard_trait (tree *expr, tree *args)
> +{
> +  if (TREE_CODE (*expr) != TEMPLATE_ID_EXPR)
> +    return;
> +
> +  tree templ = TREE_OPERAND (*expr, 0);
> +  if (TREE_CODE (templ) != TEMPLATE_DECL
> +      || !variable_template_p (templ))
> +    return;
> +
> +  tree gen_tmpl = most_general_template (templ);
> +  if (DECL_TEMPLATE_SPECIALIZATIONS (gen_tmpl))
> +    return;
> +
> +  for (tree inst = DECL_TEMPLATE_INSTANTIATIONS (gen_tmpl);
> +       inst; inst = TREE_CHAIN (inst))
> +    if (DECL_TEMPLATE_SPECIALIZATION (TREE_VALUE (inst)))
> +      return;
> +
> +  tree pattern = DECL_TEMPLATE_RESULT (gen_tmpl);
> +  tree initial = DECL_INITIAL (pattern);
> +  if (TREE_CODE (initial) != TRAIT_EXPR)
> +    return;
> +
> +  /* At this point we're definitely providing a TRAIT_EXPR, update
> +     *expr to point at it and provide remapped *args for it.  */
> +  tree parms = DECL_INNERMOST_TEMPLATE_PARMS (gen_tmpl);
> +  tree targs = TREE_OPERAND (*expr, 1);
> +  if (targs)
> +    targs = tsubst_template_args (targs, *args, tf_none, NULL_TREE);
> +  targs = add_outermost_template_args (templ, targs);
> +  targs = coerce_template_parms (parms, targs, templ, tf_none);

If we substituted the TEMPLATE_ID_EXPR as a whole we could use the
DECL_TI_ARGS of that IIUC?

> +
> +  *expr = initial;
> +  *args = targs;
> +}
> +
>  /* Diagnose a substitution failure in the atomic constraint T using ARGS.  */
>  
>  static void
> @@ -3347,10 +3390,11 @@ 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);
> +  maybe_unwrap_standard_trait (&expr, &args);
>    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/testsuite/g++.dg/cpp2a/concepts-traits4.C 
> b/gcc/testsuite/g++.dg/cpp2a/concepts-traits4.C
> new file mode 100644
> index 00000000000..a2f9609c4e1
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-traits4.C
> @@ -0,0 +1,31 @@
> +// PR c++/117294
> +// { dg-do compile { target c++20 } }
> +
> +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-message "'void' is not default constructible" "" { target *-*-* } 
> .-1 }
> +
> +template <typename T>
> +concept test_partial_spec = partial_spec<T>;
> +// { dg-message "evaluated to .false." "" { target *-*-* } .-1 }
> +// { dg-bogus "not default constructible" "" { target *-*-* } .-2 }
> +
> +template <typename T>
> +concept test_explicit_spec = explicit_spec<T>;
> +// { dg-message "evaluated to .false." "" { target *-*-* } .-1 }
> +// { dg-bogus "not default constructible" "" { target *-*-* } .-2 }
> +
> +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" }
> +
> +static_assert(test_partial_spec<int*>);  // { dg-error "assert" }
> +static_assert(test_explicit_spec<int>);  // { dg-error "assert" }
> -- 
> 2.47.0
> 
> 

Reply via email to