Thanks for the patch!
On Wed, 1 May 2024, Seyed Sajad Kahani wrote:
> When deducing auto for `adc_return_type`, `adc_variable_type`, and
> `adc_decomp_type` contexts (at the usage time), we try to resolve the
> outermost template arguments to be used for satisfaction. This is done by one
> of the following, depending on the scope:
>
> 1. Checking the `DECL_TEMPLATE_INFO` of the current function scope and
> extracting DECL_TI_ARGS from it for function scope deductions (pt.cc:31236).
> 2. Checking the `DECL_TEMPLATE_INFO` of the declaration (alongside with other
> conditions) for non-function scope variable declaration deductions
> (decl.cc:8527).
>
> Then, we do not retrieve the deeper layers of the template arguments;
> instead, we fill the missing levels with dummy levels (pt.cc:31260).
>
> The problem (that is shown in PR114915) is that we do not consider the case
> where the deduction happens in a template specialization scope. In this case,
> the type is not dependent on the outermost template arguments (which are the
> specialization arguments). Yet, we still resolve the outermost template
> arguments, and then the number of layers in the template arguments exceeds
> the number of levels in the type. This causes the missing levels to be
> negative. This leads to the rejection of valid code and ICEs (like segfault)
> in the release mode. In the debug mode, it is possible to show as an
> assertion failure (when creating a tree_vec with a negative size).
> The code that generates the issue is added to the test suite as
> `g++.dg/cpp2a/concepts-placeholder14.C`.
>
> This patch fixes the issue by checking that the template usage, whose
> arguments are going to be used for satisfaction, is not a partial or explicit
> specialization (and therefore it is an implicit or explicit instantiation).
> This check is done in the two only places that affect the `outer_targs` for
> the mentioned contexts.
>
> One might ask why this is not implemented as a simple `missing_level > 0`
> check. The reason is that the recovery from the negative `missing_levels`
> will not be easy, and it is not clear how to recover from it. Therefore, it
> is better to prevent it from happening.
> ---
> gcc/cp/decl.cc | 1 +
> gcc/cp/pt.cc | 16 ++++++++++-----
> .../g++.dg/cpp2a/concepts-placeholder14.C | 20 +++++++++++++++++++
> 3 files changed, 32 insertions(+), 5 deletions(-)
> create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-placeholder14.C
>
> diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
> index 65ab64885..7e51f926e 100644
> --- a/gcc/cp/decl.cc
> +++ b/gcc/cp/decl.cc
> @@ -8527,6 +8527,7 @@ cp_finish_decl (tree decl, tree init, bool
> init_const_expr_p,
> if (PLACEHOLDER_TYPE_CONSTRAINTS_INFO (auto_node)
> && DECL_LANG_SPECIFIC (decl)
> && DECL_TEMPLATE_INFO (decl)
> + && DECL_USE_TEMPLATE (decl) != 2
We should check !DECL_TEMPLATE_SPECIALIZATION instead of checking
DECL_USE_TEMPLATE directly.
And since DECL_TEMPLATE_SPECIALIZATION is true for both partial and
explicit specializations, I think we should check !in_template_context
as well in order to single out explicit specializations?
> && !DECL_FUNCTION_SCOPE_P (decl))
> /* The outer template arguments might be needed for satisfaction.
> (For function scope variables, do_auto_deduction will obtain the
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index 3b2106dd3..fd646d873 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -31044,7 +31044,8 @@ unparenthesized_id_or_class_member_access_p (tree
> init)
> OUTER_TARGS is used during template argument deduction (context ==
> adc_unify)
> to properly substitute the result. It's also used in the adc_unify and
> adc_requirement contexts to communicate the necessary template arguments
> - to satisfaction. OUTER_TARGS is ignored in other contexts.
> + to satisfaction. OUTER_TARGS will be used for other contexts if it is a
> + function scope deduction. Otherwise it is ignored.
>
> Additionally for adc_unify contexts TMPL is the template for which TYPE
> is a template parameter type.
> @@ -31235,8 +31236,11 @@ do_auto_deduction (tree type, tree init, tree
> auto_node,
> if (tree fn = current_function_decl)
> if (DECL_TEMPLATE_INFO (fn) || LAMBDA_FUNCTION_P (fn))
> {
> - outer_targs = DECL_TEMPLATE_INFO (fn)
> - ? DECL_TI_ARGS (fn) : NULL_TREE;
> + outer_targs = NULL_TREE;
> + if (DECL_TEMPLATE_INFO (fn) && DECL_USE_TEMPLATE (fn) != 2)
> + {
> + outer_targs = DECL_TI_ARGS (fn);
> + }
Same here.
> if (LAMBDA_FUNCTION_P (fn))
> {
> /* As in satisfy_declaration_constraints. */
> @@ -31260,8 +31264,10 @@ do_auto_deduction (tree type, tree init, tree
> auto_node,
> these missing levels, but this hack otherwise allows us to handle a
> large subset of possible constraints (including all non-dependent
> constraints). */
> - if (int missing_levels = (TEMPLATE_TYPE_ORIG_LEVEL (auto_node)
> - - TMPL_ARGS_DEPTH (full_targs)))
> + int missing_levels = (TEMPLATE_TYPE_ORIG_LEVEL (auto_node)
> + - TMPL_ARGS_DEPTH (full_targs));
> +
> + if (missing_levels > 0)
IIUC this change is not necessary with the above changes, right? Maybe
we could assert than missing_levels is nonnegative.
Another more context-unaware approach to fix this might be to only
use the innermost level from 'full_targs' for satisfaction if
TEMPLATE_TYPE_ORIG_LEVEL is 1 (which means this constrained auto
appeared in a context that doesn't have template parameters such as an
explicit specialization or ordinary non-template function, and so
only the level corresponding to the deduced type is needed for
satisfaction.)
Generalizing on that, another approach might be to handle missing_levels < 0
by removing -missing_levels from full_targs via get_innermost_template_args.
But AFAICT it should suffice to handle the TEMPLATE_TYPE_ORIG_LEVEL == 1
case specifically.
I wonder what Jason thinks?
> {
> tree dummy_levels = make_tree_vec (missing_levels);
> for (int i = 0; i < missing_levels; ++i)
> diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder14.C
> b/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder14.C
> new file mode 100644
> index 000000000..4a98ec7b3
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-placeholder14.C
> @@ -0,0 +1,20 @@
> +// PR c++/114915
> +// { dg-do compile { target c++20 } }
> +
> +template<typename T>
> +concept TheConcept = __is_same(T, int);
> +
> +template<typename T>
> +void f() {
> + TheConcept auto x = 1;
> +}
> +
> +template<>
> +void f<int>() {
> + TheConcept auto x = 1;
> +}
> +
> +int main() {
> + f<int>();
> + return 0;
> +}
It would be good to also test an explicit variable tmpl spec and
an explicit spec of a member template of a class template.
> --
> 2.44.0
>
>