forked from OSchip/llvm-project
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:
parent
dc7200b486
commit
b884ed186e
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue