Author: rsmith Date: Wed Jul 11 16:19:41 2018 New Revision: 336867 URL: http://llvm.org/viewvc/llvm-project?rev=336867&view=rev Log: Fix deduction for conversion function templates converting to reference 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. Modified: cfe/trunk/lib/Sema/SemaTemplateDeduction.cpp cfe/trunk/test/CXX/temp/temp.fct.spec/temp.deduct/temp.deduct.conv/p4.cpp Modified: cfe/trunk/lib/Sema/SemaTemplateDeduction.cpp URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Sema/SemaTemplateDeduction.cpp?rev=336867&r1=336866&r2=336867&view=diff ============================================================================== --- cfe/trunk/lib/Sema/SemaTemplateDeduction.cpp (original) +++ cfe/trunk/lib/Sema/SemaTemplateDeduction.cpp Wed Jul 11 16:19:41 2018 @@ -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 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(FunctionTe // 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(FunctionTe // 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. Modified: cfe/trunk/test/CXX/temp/temp.fct.spec/temp.deduct/temp.deduct.conv/p4.cpp URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/CXX/temp/temp.fct.spec/temp.deduct/temp.deduct.conv/p4.cpp?rev=336867&r1=336866&r2=336867&view=diff ============================================================================== --- cfe/trunk/test/CXX/temp/temp.fct.spec/temp.deduct/temp.deduct.conv/p4.cpp (original) +++ cfe/trunk/test/CXX/temp/temp.fct.spec/temp.deduct/temp.deduct.conv/p4.cpp Wed Jul 11 16:19:41 2018 @@ -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_q // 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 +} _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits