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.

llvm-svn: 336867
This commit is contained in:
Richard Smith 2018-07-11 23:19:41 +00:00
parent dc7200b486
commit b884ed186e
2 changed files with 97 additions and 4 deletions

View 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.

View 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
}