From 812798917c59e95405a71b31ab37bd78c0f43f79 Mon Sep 17 00:00:00 2001 From: Jason Merrill Date: Tue, 7 Jul 2020 01:41:35 -0400 Subject: [PATCH] c++: Allow subobject references in C++20. The last new thing allowed by P1907R1: subobject addresses as template arguments. The ABI group has discussed mangling for this; there has been some talk of a compressed subobject mangling, but it hasn't been finalized, so for now I'm using normal expression mangling. In order for two array subobject references to compare as equal template arguments, the offsets need to have the same type, so I convert them to always be the same type, currently ptrdiff_t. Base class conversions are represented as a cast to reference type, only if necessary to resolve an ambiguity. This patch also updates the value of __cpp_nontype_template_args, since the paper is fully implemented. gcc/cp/ChangeLog: * mangle.c (write_base_ref): New. (write_expression): Use it for base field COMPONENT_REFs. * pt.c (invalid_tparm_referent_p): Canonicalize the type of array offsets. Allow subobjects. gcc/c-family/ChangeLog: * c-cppbuiltin.c (c_cpp_builtins): Update __cpp_nontype_template_args for C++20. gcc/testsuite/ChangeLog: * g++.dg/cpp1z/nontype2.C: No error in C++20. * g++.dg/template/nontype25.C: No error in C++20. * g++.dg/template/nontype8.C: No error in C++20. * g++.dg/cpp2a/nontype-subob1.C: New test. * g++.dg/cpp2a/nontype-subob2.C: New test. * g++.dg/cpp1z/nontype3.C: Now C++17-only. * g++.dg/cpp2a/feat-cxx2a.C: Adjust expected value. --- gcc/c-family/c-cppbuiltin.c | 4 ++- gcc/cp/mangle.c | 56 +++++++++++++++++++++++++++++ gcc/cp/pt.c | 24 ++++++++++--- gcc/testsuite/g++.dg/cpp1z/nontype2.C | 2 +- gcc/testsuite/g++.dg/cpp1z/nontype3.C | 2 +- gcc/testsuite/g++.dg/cpp2a/feat-cxx2a.C | 4 +-- gcc/testsuite/g++.dg/cpp2a/nontype-subob1.C | 25 +++++++++++++ gcc/testsuite/g++.dg/cpp2a/nontype-subob2.C | 13 +++++++ gcc/testsuite/g++.dg/template/nontype25.C | 6 ++-- gcc/testsuite/g++.dg/template/nontype8.C | 4 +-- 10 files changed, 126 insertions(+), 14 deletions(-) create mode 100644 gcc/testsuite/g++.dg/cpp2a/nontype-subob1.C create mode 100644 gcc/testsuite/g++.dg/cpp2a/nontype-subob2.C diff --git a/gcc/c-family/c-cppbuiltin.c b/gcc/c-family/c-cppbuiltin.c index 83f52fd..74ecca8 100644 --- a/gcc/c-family/c-cppbuiltin.c +++ b/gcc/c-family/c-cppbuiltin.c @@ -967,7 +967,8 @@ c_cpp_builtins (cpp_reader *pfile) cpp_define (pfile, "__cpp_enumerator_attributes=201411L"); cpp_define (pfile, "__cpp_nested_namespace_definitions=201411L"); cpp_define (pfile, "__cpp_fold_expressions=201603L"); - cpp_define (pfile, "__cpp_nontype_template_args=201411L"); + if (cxx_dialect <= cxx17) + cpp_define (pfile, "__cpp_nontype_template_args=201411L"); cpp_define (pfile, "__cpp_range_based_for=201603L"); if (cxx_dialect <= cxx17) cpp_define (pfile, "__cpp_constexpr=201603L"); @@ -998,6 +999,7 @@ c_cpp_builtins (cpp_reader *pfile) cpp_define (pfile, "__cpp_consteval=201811L"); cpp_define (pfile, "__cpp_constinit=201907L"); cpp_define (pfile, "__cpp_deduction_guides=201907L"); + cpp_define (pfile, "__cpp_nontype_template_args=201911L"); cpp_define (pfile, "__cpp_nontype_template_parameter_class=201806L"); cpp_define (pfile, "__cpp_impl_destroying_delete=201806L"); cpp_define (pfile, "__cpp_constexpr_dynamic_alloc=201907L"); diff --git a/gcc/cp/mangle.c b/gcc/cp/mangle.c index ab2d8ec..43ff2e8 100644 --- a/gcc/cp/mangle.c +++ b/gcc/cp/mangle.c @@ -2850,6 +2850,60 @@ write_member_name (tree member) write_expression (member); } +/* EXPR is a base COMPONENT_REF; write the minimized base conversion path for + converting to BASE, or just the conversion of EXPR if BASE is null. + + "Given a fully explicit base path P := C_n -> ... -> C_0, the minimized base + path Min(P) is defined as follows: let C_i be the last element for which the + conversion to C_0 is unambiguous; if that element is C_n, the minimized path + is C_n -> C_0; otherwise, the minimized path is Min(C_n -> ... -> C_i) -> + C_0." + + We mangle the conversion to C_i if it's different from C_n. */ + +static bool +write_base_ref (tree expr, tree base = NULL_TREE) +{ + if (TREE_CODE (expr) != COMPONENT_REF) + return false; + + tree field = TREE_OPERAND (expr, 1); + + if (TREE_CODE (field) != FIELD_DECL || !DECL_FIELD_IS_BASE (field)) + return false; + + tree object = TREE_OPERAND (expr, 0); + + tree binfo = NULL_TREE; + if (base) + { + tree cur = TREE_TYPE (object); + binfo = lookup_base (cur, base, ba_unique, NULL, tf_none); + } + else + /* We're at the end of the base conversion chain, so it can't be + ambiguous. */ + base = TREE_TYPE (field); + + if (binfo == error_mark_node) + { + /* cur->base is ambiguous, so make the conversion to + last explicit, expressed as a cast (last&)object. */ + tree last = TREE_TYPE (expr); + write_string (OVL_OP_INFO (false, CAST_EXPR)->mangled_name); + write_type (build_reference_type (last)); + write_expression (object); + } + else if (write_base_ref (object, base)) + /* cur->base is unambiguous, but we had another base conversion + underneath and wrote it out. */; + else + /* No more base conversions, just write out the object. */ + write_expression (object); + + return true; +} + /* ::= ::= ::= @@ -3268,6 +3322,8 @@ write_expression (tree expr) ob = TREE_OPERAND (ob, 0); write_expression (ob); } + else if (write_base_ref (expr)) + return; else if (!is_dummy_object (ob)) { write_string ("dt"); diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c index 5f43e9c..cfe5dcd 100644 --- a/gcc/cp/pt.c +++ b/gcc/cp/pt.c @@ -6907,7 +6907,9 @@ template_parm_object_p (const_tree t) } /* Subroutine of convert_nontype_argument, to check whether EXPR, as an - argument for TYPE, points to an unsuitable object. */ + argument for TYPE, points to an unsuitable object. + + Also adjust the type of the index in C++20 array subobject references. */ static bool invalid_tparm_referent_p (tree type, tree expr, tsubst_flags_t complain) @@ -6935,6 +6937,19 @@ invalid_tparm_referent_p (tree type, tree expr, tsubst_flags_t complain) { tree decl = TREE_OPERAND (expr, 0); + if (cxx_dialect >= cxx20) + while (TREE_CODE (decl) == COMPONENT_REF + || TREE_CODE (decl) == ARRAY_REF) + { + tree &op = TREE_OPERAND (decl, 1); + if (TREE_CODE (decl) == ARRAY_REF + && TREE_CODE (op) == INTEGER_CST) + /* Canonicalize array offsets to ptrdiff_t; how they were + written doesn't matter for subobject identity. */ + op = fold_convert (ptrdiff_type_node, op); + decl = TREE_OPERAND (decl, 0); + } + if (!VAR_P (decl)) { if (complain & tf_error) @@ -6976,9 +6991,10 @@ invalid_tparm_referent_p (tree type, tree expr, tsubst_flags_t complain) decl); return true; } - else if (!same_type_ignoring_top_level_qualifiers_p - (strip_array_types (TREE_TYPE (type)), - strip_array_types (TREE_TYPE (decl)))) + else if (cxx_dialect < cxx20 + && !(same_type_ignoring_top_level_qualifiers_p + (strip_array_types (TREE_TYPE (type)), + strip_array_types (TREE_TYPE (decl))))) { if (complain & tf_error) error ("the address of the %qT subobject of %qD is not a " diff --git a/gcc/testsuite/g++.dg/cpp1z/nontype2.C b/gcc/testsuite/g++.dg/cpp1z/nontype2.C index 75dc760..cf73166 100644 --- a/gcc/testsuite/g++.dg/cpp1z/nontype2.C +++ b/gcc/testsuite/g++.dg/cpp1z/nontype2.C @@ -8,7 +8,7 @@ template class X { }; template class Y {}; template class Z {}; -X<&s.m> x7; // { dg-error "3:.& s.S::m. is not a valid template argument" } +X<&s.m> x7; // { dg-error "3:.& s.S::m. is not a valid template argument" "" { target c++17_down } } Y<"foo"> y1; // { dg-error "string literal" } Z z1; // { dg-error "" } diff --git a/gcc/testsuite/g++.dg/cpp1z/nontype3.C b/gcc/testsuite/g++.dg/cpp1z/nontype3.C index 80f1e98..1e02f1e 100644 --- a/gcc/testsuite/g++.dg/cpp1z/nontype3.C +++ b/gcc/testsuite/g++.dg/cpp1z/nontype3.C @@ -1,4 +1,4 @@ -// { dg-do compile { target c++17 } } +// { dg-do compile { target c++17_only } } #ifndef __cpp_nontype_template_args #error __cpp_nontype_template_args not defined diff --git a/gcc/testsuite/g++.dg/cpp2a/feat-cxx2a.C b/gcc/testsuite/g++.dg/cpp2a/feat-cxx2a.C index 82fd602..7f1fe34 100644 --- a/gcc/testsuite/g++.dg/cpp2a/feat-cxx2a.C +++ b/gcc/testsuite/g++.dg/cpp2a/feat-cxx2a.C @@ -334,8 +334,8 @@ #ifndef __cpp_nontype_template_args # error "__cpp_nontype_template_args" -#elif __cpp_nontype_template_args != 201411 -# error "__cpp_nontype_template_args != 201411" +#elif __cpp_nontype_template_args != 201911 +# error "__cpp_nontype_template_args != 201911" #endif #ifndef __cpp_hex_float diff --git a/gcc/testsuite/g++.dg/cpp2a/nontype-subob1.C b/gcc/testsuite/g++.dg/cpp2a/nontype-subob1.C new file mode 100644 index 0000000..4c1633e --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/nontype-subob1.C @@ -0,0 +1,25 @@ +// { dg-do compile { target c++20 } } + +template struct A {}; +template struct assert_same; +template struct assert_same {}; + +#define TEQ(X,Y) static_assert(__is_same(A<(X)>,A<(Y)>)) +#define TNEQ(X,Y) static_assert(!__is_same(A<(X)>,A<(Y)>)) + +struct C { int i; }; + +struct B: C +{ + int j[3]; +} b; + +// { dg-final { scan-assembler _Z1f1AIXaddtL_Z1bE1iEE } } +void f(A<&b.i>) {} +TEQ(&b.i,&((C*)&b)->i); + +// { dg-final { scan-assembler _Z1g1AIXadixdtL_Z1bE1jLl1EEE } } +void g(A<&b.j[0]+1>) {} +TEQ(&b.j[1],&b.j[1]); +TEQ(&b.j[1],&b.j[0]+1); +TNEQ(&b.j[1],&b.j[0]); diff --git a/gcc/testsuite/g++.dg/cpp2a/nontype-subob2.C b/gcc/testsuite/g++.dg/cpp2a/nontype-subob2.C new file mode 100644 index 0000000..a5768f6 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/nontype-subob2.C @@ -0,0 +1,13 @@ +// { dg-do compile { target c++20 } } +// { dg-additional-options -Wno-inaccessible-base } + +struct Base { int i; }; +template struct Derived : Derived, Base {}; +template <> struct Derived<0> : Base {}; + +template struct A { }; + +Derived<4> d; +void f(A<&((Derived<0>&)d).i>) {} + +// { dg-final { scan-assembler _Z1f1AIXaddtcvR7DerivedILi0EEL_Z1dE1iEE } } diff --git a/gcc/testsuite/g++.dg/template/nontype25.C b/gcc/testsuite/g++.dg/template/nontype25.C index 3918bea..aba8509 100644 --- a/gcc/testsuite/g++.dg/template/nontype25.C +++ b/gcc/testsuite/g++.dg/template/nontype25.C @@ -7,16 +7,16 @@ template class C {}; template class D {}; template class E {}; -template void f(D &, C(b)> &) {} // { dg-error "" } +template void f(D &, C(b)> &) {} // { dg-error "" "" { target { ! c++20 } } } template void g(D &, E(b)> &) {} // { dg-error "" "" { target { ! c++11 } } } B b; int main() { - C(&b)> c; // { dg-error "" } + C(&b)> c; // { dg-error "" "" { target c++17_down } } D<&b> d; E(&b)> e; // { dg-error "" "" { target { ! c++11 } } } - f(d, c); // { dg-error "" "" { target c++11 } } + f(d, c); // { dg-error "" "" { target { c++11 && { ! c++20 } } } } g(d, e); } diff --git a/gcc/testsuite/g++.dg/template/nontype8.C b/gcc/testsuite/g++.dg/template/nontype8.C index b4fbeae..a0458b9 100644 --- a/gcc/testsuite/g++.dg/template/nontype8.C +++ b/gcc/testsuite/g++.dg/template/nontype8.C @@ -6,9 +6,9 @@ template class X { }; int a[10]; struct S { int m; static int s; } s; -X<&a[2]> x3; // { dg-error "3:.& a\\\[2\\\]. is not a valid template argument" "" { target c++17 } } +X<&a[2]> x3; // { dg-error "3:.& a\\\[2\\\]. is not a valid template argument" "" { target c++17_only } } // { dg-error "" "" { target c++14_down } .-1 } -X<&s.m> x4; // { dg-error "3:.& s.S::m. is not a valid template argument" "" { target c++17 } } +X<&s.m> x4; // { dg-error "3:.& s.S::m. is not a valid template argument" "" { target c++17_only } } // { dg-error "" "" { target c++14_down } .-1 } X<&s.s> x5; // { dg-error "" "" { target { ! c++17 } } } &S::s must be used X<&S::s> x6; // OK: address of static member -- 2.7.4