It turned out that DR 2352 needs some fixing: as of now, it means that in void f(int*); // #1 void f(const int* const &); // #2 void g(int* p) { f(p); }
the call to f is ambiguous. This broke e.g. Boost. I raised this on the CWG reflector and Jason suggested wording that would break this up; this patch is an attempt to implement that suggestion. Jason, you remarked that adding a ck_qual under the ck_ref_bind might be too much trouble, but it seems it's actually fairly simple. The comments hopefully explain my thinking. Is this what you had in mind? ref-bind6.C is something reduced from libstdc++ I came across when testing this patch. Bootstrapped/regtested on x86_64-linux, ok for trunk? 2019-09-26 Marek Polacek <pola...@redhat.com> PR c++/91889 - follow-up fix for DR 2352. * call.c (involves_qualification_conversion_p): New function. (direct_reference_binding): Build a ck_qual if the conversion would involve a qualification conversion. (convert_like_real): Strip the conversion created by the ck_qual in direct_reference_binding. * g++.dg/cpp0x/ref-bind3.C: Add dg-error. * g++.dg/cpp0x/ref-bind4.C: New test. * g++.dg/cpp0x/ref-bind5.C: New test. * g++.dg/cpp0x/ref-bind6.C: New test. * g++.old-deja/g++.pt/spec35.C: Revert earlier change. diff --git gcc/cp/call.c gcc/cp/call.c index 45b984ecb11..09976369aed 100644 --- gcc/cp/call.c +++ gcc/cp/call.c @@ -1555,6 +1555,27 @@ reference_compatible_p (tree t1, tree t2) return true; } +/* Return true if converting FROM to TO would involve a qualification + conversion. */ + +static bool +involves_qualification_conversion_p (tree to, tree from) +{ + /* If we're not convering a pointer to another one, we won't get + a qualification conversion. */ + if (!((TYPE_PTR_P (to) && TYPE_PTR_P (from)) + || (TYPE_PTRDATAMEM_P (to) && TYPE_PTRDATAMEM_P (from)))) + return false; + + conversion *conv = standard_conversion (to, from, NULL_TREE, + /*c_cast_p=*/false, 0, tf_none); + for (conversion *t = conv; t; t = next_conversion (t)) + if (t->kind == ck_qual) + return true; + + return false; +} + /* A reference of the indicated TYPE is being bound directly to the expression represented by the implicit conversion sequence CONV. Return a conversion sequence for this binding. */ @@ -1598,6 +1619,19 @@ direct_reference_binding (tree type, conversion *conv) That way, convert_like knows not to generate a temporary. */ conv->need_temporary_p = false; } + else if (involves_qualification_conversion_p (t, conv->type)) + /* Represent the qualification conversion. After DR 2352 + #1 and #2 were indistinguishable conversion sequences: + + void f(int*); // #1 + void f(const int* const &); // #2 + void g(int* p) { f(p); } + + because the types "int *" and "const int *const" are + reference-related and we were binding both directly and they + had the same rank. To break it up, we add a ck_qual under the + ck_ref_bind so that conversion sequence ranking chooses #1. */ + conv = build_conv (ck_qual, t, conv); return build_conv (ck_ref_bind, type, conv); } @@ -7342,6 +7376,16 @@ convert_like_real (conversion *convs, tree expr, tree fn, int argnum, { tree ref_type = totype; + /* direct_reference_binding might have inserted a ck_qual under + this ck_ref_bind for the benefit of conversion sequence ranking. + Ignore the conversion; we'll create our own below. */ + if (next_conversion (convs)->kind == ck_qual) + { + gcc_assert (same_type_p (TREE_TYPE (expr), + next_conversion (convs)->type)); + STRIP_NOPS (expr); + } + if (convs->bad_p && !next_conversion (convs)->bad_p) { tree extype = TREE_TYPE (expr); diff --git gcc/testsuite/g++.dg/cpp0x/ref-bind3.C gcc/testsuite/g++.dg/cpp0x/ref-bind3.C index 16e1bfe6ccc..b2c85ec684a 100644 --- gcc/testsuite/g++.dg/cpp0x/ref-bind3.C +++ gcc/testsuite/g++.dg/cpp0x/ref-bind3.C @@ -1,22 +1,18 @@ // PR c++/91844 - Implement CWG 2352, Similar types and reference binding. // { dg-do compile { target c++11 } } -template<typename T> int f (const T *const &); // (1) -template<typename T> int f (T *const &); // (2) -template<typename T> int f (T *); // (3) - -/* Before CWG 2352, (2) was a better match than (1), but (2) and (3) were - equally good, so there was an ambiguity. (2) was better than (1) because - (1) required a qualification conversion whereas (2) didn't. But with this - CWG, (1) no longer requires a qualification conversion, because the types - "const int* const" and "int *" are now considered reference-related and we - bind directly, and (1) is more specialized than (2). And (1) is also a - better match than (3). */ +template<typename T> int f (const T *const &); // 1 +template<typename T> int f (T *const &); // 2 +template<typename T> int f (T *); // 3 +/* There's an ambiguity: (2) is a better match than (1) because + (1) requires a qualification conversion whereas (2) doesn't, but + (2) and (3) are indistinguishable conversion sequences. */ + void g (int *p, const int *q, const int *const r) { - f (p); // calls (1) - f (q); // calls (1) - f (r); // calls (1) + f (p); // { dg-error "call of overloaded" } + f (q); + f (r); } diff --git gcc/testsuite/g++.dg/cpp0x/ref-bind4.C gcc/testsuite/g++.dg/cpp0x/ref-bind4.C new file mode 100644 index 00000000000..85ac9fbfd79 --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/ref-bind4.C @@ -0,0 +1,56 @@ +// PR c++/91889 - follow-up fix for DR 2352. +// { dg-do compile { target c++11 } } + +int i; + +void f1 (int *); +void f1 (const int *const &); + +void f2 (int *); +void f2 (const int *&); + +void f3 (const int *); +void f3 (int *const &); + +void f4 (int *&); +void f4 (int *const &); + +void f5 (const int *&); +void f5 (int *const &); + +void f6 (int *const &); +void f6 (const int *const &); + +void f7 (int **const); +void f7 (const int *const *const &); + +void f8 (const int *const *); +void f8 (const int *const *const &); + +void f9 (int *const *); +void f9 (const int *const *const &); + +void +g (int *p, const int *pc, const int **q) +{ + f1 (p); + f1 (pc); + f2 (p); + f2 (pc); + f3 (p); + f3 (pc); + f4 (p); + f5 (p); + f5 (pc); + f6 (p); + f6 (pc); + f7 (q); + /* [over.ics.rank] + + --S1 and S2 differ only in their qualification conversion and yield + similar types T1 and T2 (_conv.qual_), respectively, and the cv- + qualification signature of type T1 is a proper subset of the cv- + qualification signature of type T2 */ + f8 (q); + f9 (q); +} diff --git gcc/testsuite/g++.dg/cpp0x/ref-bind5.C gcc/testsuite/g++.dg/cpp0x/ref-bind5.C new file mode 100644 index 00000000000..d528b87761d --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/ref-bind5.C @@ -0,0 +1,17 @@ +// PR c++/91889 - follow-up fix for DR 2352. +// { dg-do compile { target c++11 } } + +template <typename U> struct A { typedef U *type; }; +struct B { + typedef A<B>::type node_ptr; +}; +struct C { + typedef B::node_ptr node_ptr; + typedef A<const B>::type const_node_ptr; +}; +struct { + void to_value_ptr(C::node_ptr) {}; + void to_value_ptr(const C::const_node_ptr &); +} b; +C::node_ptr a; +void fn1() { b.to_value_ptr(a); } diff --git gcc/testsuite/g++.dg/cpp0x/ref-bind6.C gcc/testsuite/g++.dg/cpp0x/ref-bind6.C new file mode 100644 index 00000000000..c85a5cfb36f --- /dev/null +++ gcc/testsuite/g++.dg/cpp0x/ref-bind6.C @@ -0,0 +1,12 @@ +// PR c++/91889 - follow-up fix for DR 2352. +// { dg-do compile { target c++11 } } + +template <typename T> struct A { + A(const T &); +}; + +struct { + int *m; +} a; + +void fn1() { A<const int *>(a.m); } diff --git gcc/testsuite/g++.old-deja/g++.pt/spec35.C gcc/testsuite/g++.old-deja/g++.pt/spec35.C index 93e953df7e7..1debf915fe9 100644 --- gcc/testsuite/g++.old-deja/g++.pt/spec35.C +++ gcc/testsuite/g++.old-deja/g++.pt/spec35.C @@ -14,9 +14,9 @@ template <typename T> int Foo (T &); // { dg-message "note" } candidate template <typename T> int Qux (T); // { dg-message "note" } template <typename T> int Qux (T const &); // { dg-message "note" } candidate -template <typename T> int Bar (T const *const &); -template <typename T> int Bar (T *const &); -template <typename T> int Bar (T *); +template <typename T> int Bar (T const *const &); // { dg-message "note" } +template <typename T> int Bar (T *const &); // { dg-message "note" } candidate +template <typename T> int Bar (T *); // { dg-message "note" } candidate template <typename T> int Baz (T *const &); // { dg-message "note" } template <typename T> int Baz (T *); // { dg-message "note" } candidate @@ -24,7 +24,7 @@ template <typename T> int Baz (T *); // { dg-message "note" } candi int Baz (int const *ptr, int *ptr2) { Baz (ptr2); // { dg-error "ambiguous" } - Bar (ptr2); + Bar (ptr2); // { dg-error "ambiguous" } Foo (ptr2); // { dg-error "ambiguous" } Qux (ptr2); // { dg-error "ambiguous" } return 0;