This patch fixes AST comparison for trailing return types using dependent sizeof/alignof/noexcept expressions as template value arguments. I believe this bug is over a decade old, hailing from GCC 4.6. I found it over 5 years ago and sat on the repro until I had time to fix it myself.
The new test cases demonstrate redeclarations mistakenly parsed as ambiguous overloads. These fail with gcc, but pass with clang. The last test case concerns a GNU extension for alignof (hence the -Wno-pedantic). After applying this patch, bootstrap succeeds, the new tests pass, and my native "make -k check" outputs look essentially similar to these based on the same commit: https://gcc.gnu.org/pipermail/gcc-testresults/2021-August/715032.html (except I only waited for a few thousand of the libstdc++ tests to pass). I think it makes sense to now split the switch-case blocks here since there isn't much shared logic left, and I have a patch to do that if desired, but I think the patch below is the cleanest for initial review. I studied the history and churn of the comparing_specializations hacks. While I am still not entirely certain this is the correct change to make there, the change seems at least superficially reasonable to me. I haven't been able to break it yet. Perhaps someone more familiar with this area of the compiler can weigh in. See also the existing cp_unevaluated_operand push/pop for these EXPR codes in cp_walk_tree. I am awaiting a response to the copyright assignment request sent to ass...@gnu.org. commit fe64ca143dfb9021b4c47abb6382827c13e1f0cd Author: Barrett Adair <barrettellisad...@gmail.com> Date: Fri Aug 20 15:37:36 2021 -0500 Fix cp_tree_equal for template value args using dependent sizeof/alignof/noexcept expressions diff --git a/gcc/cp/tree.c b/gcc/cp/tree.c index 3c62dd74380..fce2a46cfa8 100644 --- a/gcc/cp/tree.c +++ b/gcc/cp/tree.c @@ -3903,40 +3903,41 @@ cp_tree_equal (tree t1, tree t2) as being equivalent to anything. */ if (VAR_P (o1) && DECL_NAME (o1) == NULL_TREE && !DECL_RTL_SET_P (o1)) /*Nop*/; else if (VAR_P (o2) && DECL_NAME (o2) == NULL_TREE && !DECL_RTL_SET_P (o2)) /*Nop*/; else if (!cp_tree_equal (o1, o2)) return false; return cp_tree_equal (TREE_OPERAND (t1, 1), TREE_OPERAND (t2, 1)); } case PARM_DECL: /* For comparing uses of parameters in late-specified return types with an out-of-class definition of the function, but can also come up for expressions that involve 'this' in a member function template. */ if (comparing_specializations + && !cp_unevaluated_operand && DECL_CONTEXT (t1) != DECL_CONTEXT (t2)) /* When comparing hash table entries, only an exact match is good enough; we don't want to replace 'this' with the version from another function. But be more flexible with parameters with identical contexts. */ return false; if (same_type_p (TREE_TYPE (t1), TREE_TYPE (t2))) { if (DECL_ARTIFICIAL (t1) ^ DECL_ARTIFICIAL (t2)) return false; if (CONSTRAINT_VAR_P (t1) ^ CONSTRAINT_VAR_P (t2)) return false; if (DECL_ARTIFICIAL (t1) || (DECL_PARM_LEVEL (t1) == DECL_PARM_LEVEL (t2) && DECL_PARM_INDEX (t1) == DECL_PARM_INDEX (t2))) return true; } return false; @@ -3974,63 +3975,68 @@ cp_tree_equal (tree t1, tree t2) return true; case CONSTRAINT_INFO: return cp_tree_equal (CI_ASSOCIATED_CONSTRAINTS (t1), CI_ASSOCIATED_CONSTRAINTS (t2)); case CHECK_CONSTR: return (CHECK_CONSTR_CONCEPT (t1) == CHECK_CONSTR_CONCEPT (t2) && comp_template_args (CHECK_CONSTR_ARGS (t1), CHECK_CONSTR_ARGS (t2))); case TREE_VEC: /* These are template args. Really we should be getting the caller to do this as it knows it to be true. */ if (!comp_template_args (t1, t2, NULL, NULL, false)) return false; return true; case SIZEOF_EXPR: case ALIGNOF_EXPR: + case NOEXCEPT_EXPR: { tree o1 = TREE_OPERAND (t1, 0); tree o2 = TREE_OPERAND (t2, 0); if (code1 == SIZEOF_EXPR) { if (SIZEOF_EXPR_TYPE_P (t1)) o1 = TREE_TYPE (o1); if (SIZEOF_EXPR_TYPE_P (t2)) o2 = TREE_TYPE (o2); } - else if (ALIGNOF_EXPR_STD_P (t1) != ALIGNOF_EXPR_STD_P (t2)) + else if (code1 == ALIGNOF_EXPR + && ALIGNOF_EXPR_STD_P (t1) != ALIGNOF_EXPR_STD_P (t2)) return false; if (TREE_CODE (o1) != TREE_CODE (o2)) return false; - if (ARGUMENT_PACK_P (o1)) - return template_args_equal (o1, o2); - else if (TYPE_P (o1)) - return same_type_p (o1, o2); - else - return cp_tree_equal (o1, o2); + { + cp_unevaluated ev; + if (code1 == SIZEOF_EXPR && ARGUMENT_PACK_P (o1)) + return template_args_equal (o1, o2); + else if (code1 != NOEXCEPT_EXPR && TYPE_P (o1)) + return same_type_p (o1, o2); + else + return cp_tree_equal (o1, o2); + } } case MODOP_EXPR: { tree t1_op1, t2_op1; if (!cp_tree_equal (TREE_OPERAND (t1, 0), TREE_OPERAND (t2, 0))) return false; t1_op1 = TREE_OPERAND (t1, 1); t2_op1 = TREE_OPERAND (t2, 1); if (TREE_CODE (t1_op1) != TREE_CODE (t2_op1)) return false; return cp_tree_equal (TREE_OPERAND (t1, 2), TREE_OPERAND (t2, 2)); } case PTRMEM_CST: /* Two pointer-to-members are the same if they point to the same field or function in the same class. */ diff --git a/gcc/testsuite/g++.dg/template/canon-type-15.C b/gcc/testsuite/g++.dg/template/canon-type-15.C new file mode 100644 index 00000000000..064e14c510d --- /dev/null +++ b/gcc/testsuite/g++.dg/template/canon-type-15.C @@ -0,0 +1,5 @@ +// { dg-do compile { target c++11 } } +template<unsigned u> struct size_c{ static constexpr unsigned value = u; }; +template<class T> auto return_size(T t) -> size_c<sizeof(t)>; +template<class T> auto return_size(T t) -> size_c<sizeof(t)>; +static_assert(decltype(return_size('a'))::value == 1u, ""); diff --git a/gcc/testsuite/g++.dg/template/canon-type-16.C b/gcc/testsuite/g++.dg/template/canon-type-16.C new file mode 100644 index 00000000000..99361cbac30 --- /dev/null +++ b/gcc/testsuite/g++.dg/template/canon-type-16.C @@ -0,0 +1,6 @@ +// { dg-do compile { target c++11 } } +template<bool u> struct bool_c{ static constexpr bool value = u; }; +template<class T> auto noexcepty(T t) -> bool_c<noexcept(t())>; +template<class T> auto noexcepty(T t) -> bool_c<noexcept(t())>; +struct foo { void operator()() noexcept; }; +static_assert(decltype(noexcepty(foo{}))::value, ""); diff --git a/gcc/testsuite/g++.dg/template/canon-type-17.C b/gcc/testsuite/g++.dg/template/canon-type-17.C new file mode 100644 index 00000000000..0555c8d0a42 --- /dev/null +++ b/gcc/testsuite/g++.dg/template/canon-type-17.C @@ -0,0 +1,5 @@ +// { dg-do compile { target c++11 } } +template<unsigned u> struct size_c{ static constexpr unsigned value = u; }; +template<class... T> auto return_size(T... t) -> size_c<sizeof...(t)>; +template<class... T> auto return_size(T... t) -> size_c<sizeof...(t)>; +static_assert(decltype(return_size('a'))::value == 1u, ""); diff --git a/gcc/testsuite/g++.dg/template/canon-type-18.C b/gcc/testsuite/g++.dg/template/canon-type-18.C new file mode 100644 index 00000000000..2510181725c --- /dev/null +++ b/gcc/testsuite/g++.dg/template/canon-type-18.C @@ -0,0 +1,6 @@ +// { dg-do compile { target c++11 } } +// { dg-options "-Wno-pedantic" } +template<unsigned u> struct size_c{ static constexpr unsigned value = u; }; +template<class T> auto get_align(T t) -> size_c<alignof(t)>; +template<class T> auto get_align(T t) -> size_c<alignof(t)>; +static_assert(decltype(get_align('a'))::value == 1u, "");