]> granicus.if.org Git - clang/commitdiff
Fix deduction for conversion function templates converting to reference
authorRichard Smith <richard-llvm@metafoo.co.uk>
Wed, 11 Jul 2018 23:19:41 +0000 (23:19 +0000)
committerRichard Smith <richard-llvm@metafoo.co.uk>
Wed, 11 Jul 2018 23:19:41 +0000 (23:19 +0000)
types.

We previously tried to use the "parameter is a reference" logic here,
but that doesn't work because it gets P and A backwards. Instead, add
a separate implementation of the "deduced A can be less qualified than
A" rule.

This also exposes that we incorrectly stripped cv-qualifiers from the
referent of A if it was a reference. However, if we don't do that, we
get the wrong results when P is a reference. In an attempt to match
what sanity dictates and what other implementations are doing, we now
remove cv-qualifiers from A and P unless both are reference types. I've
brought this up on the core reflector too, to try to get the standard
fixed.

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@336867 91177308-0d34-0410-b5e6-96231b3b80d8

lib/Sema/SemaTemplateDeduction.cpp
test/CXX/temp/temp.fct.spec/temp.deduct/temp.deduct.conv/p4.cpp

index 760207fece572762373f8bf327beb61754ea07e7..5c3f2ca32ce9abc0481dac3970b9d70bd8944089 100644 (file)
@@ -99,6 +99,11 @@ namespace clang {
     /// deduction where the parameter is a function type that can be converted
     /// to the argument type.
     TDF_AllowCompatibleFunctionType = 0x20,
+
+    /// Within template argument deduction for a conversion function, we are
+    /// matching with an argument type for which the original argument was
+    /// a reference.
+    TDF_ArgWithReferenceType = 0x40,
   };
 }
 
@@ -1354,6 +1359,18 @@ DeduceTemplateArgumentsByTypeMatch(Sema &S,
     if (TDF & TDF_ParamWithReferenceType) {
       if (hasInconsistentOrSupersetQualifiersOf(Param, Arg))
         return Sema::TDK_NonDeducedMismatch;
+    } else if (TDF & TDF_ArgWithReferenceType) {
+      // C++ [temp.deduct.conv]p4:
+      //   If the original A is a reference type, A can be more cv-qualified
+      //   than the deduced A
+      if (!Arg.getQualifiers().compatiblyIncludes(Param.getQualifiers()))
+        return Sema::TDK_NonDeducedMismatch;
+
+      // Strip out all extra qualifiers from the argument to figure out the
+      // type we're converting to, prior to the qualification conversion.
+      Qualifiers Quals;
+      Arg = S.Context.getUnqualifiedArrayType(Arg, Quals);
+      Arg = S.Context.getQualifiedType(Arg, Param.getQualifiers());
     } else if (!IsPossiblyOpaquelyQualifiedType(Param)) {
       if (Param.getCVRQualifiers() != Arg.getCVRQualifiers())
         return Sema::TDK_NonDeducedMismatch;
@@ -4025,12 +4042,20 @@ Sema::DeduceTemplateArguments(FunctionTemplateDecl *ConversionTemplate,
   // C++0x [temp.deduct.conv]p4:
   //   [...] If A is a reference type, the type referred to by A is used
   //   for type deduction.
-  if (const ReferenceType *ARef = A->getAs<ReferenceType>())
-    A = ARef->getPointeeType().getUnqualifiedType();
+  if (const ReferenceType *ARef = A->getAs<ReferenceType>()) {
+    A = ARef->getPointeeType();
+    // We work around a defect in the standard here: cv-qualifiers are also
+    // removed from P and A in this case, unless P was a reference type. This
+    // seems to mostly match what other compilers are doing.
+    if (!FromType->getAs<ReferenceType>()) {
+      A = A.getUnqualifiedType();
+      P = P.getUnqualifiedType();
+    }
+
   // C++ [temp.deduct.conv]p3:
   //
   //   If A is not a reference type:
-  else {
+  else {
     assert(!A->isReferenceType() && "Reference types were handled above");
 
     //   - If P is an array type, the pointer type produced by the
@@ -4079,7 +4104,7 @@ Sema::DeduceTemplateArguments(FunctionTemplateDecl *ConversionTemplate,
   //       cv-qualified than the deduced A (i.e., the type referred to
   //       by the reference)
   if (ToType->isReferenceType())
-    TDF |= TDF_ParamWithReferenceType;
+    TDF |= TDF_ArgWithReferenceType;
   //     - The deduced A can be another pointer or pointer to member
   //       type that can be converted to A via a qualification
   //       conversion.
index b45ed96d14456146a759a598e570002a419a7dba..89478ed39f67a0fe8307e0e5f6230ce96f879c9d 100644 (file)
@@ -1,4 +1,6 @@
 // RUN: %clang_cc1 -fsyntax-only %s -verify
+// RUN: %clang_cc1 -std=c++11 -fsyntax-only %s -verify
+// RUN: %clang_cc1 -std=c++17 -fsyntax-only %s -verify
 
 struct AnyT {
   template<typename T>
@@ -62,3 +64,69 @@ void test_deduce_two_level_ptrmem_with_qual(TwoLevelPtrMem apm) {
   // deduce T = 'const double'
   const double X::* X::* pm1 = apm; // expected-note {{instantiation of}}
 }
+
+namespace non_ptr_ref_cv_qual {
+  template<typename Expected>
+  struct ConvToT {
+    template<typename T> operator T() {
+      using Check = T;
+      using Check = Expected;
+    }
+  };
+  const int test_conv_to_t_1 = ConvToT<int>();
+  // We intentionally deviate from [temp.deduct.conv]p4 here, and also remove
+  // the top-level cv-quaifiers from A *after* removing the reference type, if
+  // P is not also a reference type. This matches what other compilers are
+  // doing, and is necessary to support real-world code.
+  const int &test_conv_to_t_2 = ConvToT<int>();
+
+  // Example code that would be broken by the standard's rule.
+  struct Dest {};
+  Dest d1a((ConvToT<Dest>()));
+  Dest d1b = ConvToT<Dest>();
+  Dest &d2 = (d1a = ConvToT<Dest>());
+
+  template<typename Expected>
+  struct ConvToTRef {
+    template<typename T> operator T&() {
+      using Check = T;
+      using Check = Expected;
+    }
+  };
+  const int test_conv_to_t_ref_1 = ConvToTRef<int>();
+  const int &test_conv_to_t_ref_2 = ConvToTRef<const int>();
+
+  Dest d3a((ConvToTRef<const Dest>())); // initialize the copy ctor parameter with 'const Dest&'
+  Dest d3b = ConvToTRef<Dest>(); // convert to non-const T via [over.match.copy]/1.2
+  Dest &d4 = (d3a = ConvToTRef<const Dest>());
+
+  template<typename Expected>
+  struct ConvToConstT {
+    template<typename T> operator const T() {
+      using Check = T;
+      using Check = Expected;
+    }
+  };
+  const int test_conv_to_const_t_1 = ConvToConstT<int>();
+  const int &test_conv_to_const_t_2 = ConvToConstT<int>();
+
+  template<typename Expected>
+  struct ConvToConstTRef {
+    template<typename T> operator const T&() {
+      using Check = T;
+      using Check = Expected;
+    }
+  };
+  const int test_conv_to_const_t_ref_1 = ConvToConstTRef<int>();
+  const int &test_conv_to_const_t_ref_2 = ConvToConstTRef<int>();
+
+  template <typename T, int N> using Arr = T[N];
+  struct ConvToArr {
+    template <int N>
+    operator Arr<int, N> &() {
+      static_assert(N == 3, "");
+    }
+  };
+  int (&test_conv_to_arr_1)[3] = ConvToArr(); // ok
+  const int (&test_conv_to_arr_2)[3] = ConvToArr(); // ok, with qualification conversion
+}