Mordante created this revision. Mordante added reviewers: lvoufo, rsmith. Mordante added a project: clang. Mordante requested review of this revision.
The overload resolution for initializer lists was incomplete. It did not properly take the number of elements in the target array into account. The size comparison already allows for an array of unknown boud, per P0338 'Permit conversions to arrays of unknown bound'. However currently this is dead code. This fixes the overload resolution for cases like: void f(int(&&)[2]); void g() { f({1}); f({1, 2}); f({1, 2, 3}); } The patch adds extra members to `ImplicitConversionSequence`, making it larger for members only used for an initializer list. The largest member of the class' union is `UserDefinedConversion`. An initializer list can be used with a user defined conversion sequence, so it's not possible to use unused space in the union. I'm open to suggestions how to avoid the enlargement of the class. Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D87563 Files: clang/include/clang/Sema/Overload.h clang/lib/Sema/SemaOverload.cpp clang/test/SemaCXX/overload-call.cpp
Index: clang/test/SemaCXX/overload-call.cpp =================================================================== --- clang/test/SemaCXX/overload-call.cpp +++ clang/test/SemaCXX/overload-call.cpp @@ -688,3 +688,37 @@ f(pmf); } } + +#if __cplusplus >= 201103L +namespace InitializerListToArray { +void f(int(&&)[2]); // expected-note {{candidate function not viable}} +void f(int(&&)[3]); // expected-note {{candidate function not viable}} +void f(int(&&)[4]); // expected-note {{candidate function not viable}} + +void g() { + f({1}); + f({1, 2}); + f({1, 2, 3}); + f({1, 2, 3, 4}); + f({1, 2, 3, 4, 5}); // expected-error {{no matching function for call to 'f'}} +} + +struct S { + S(); + S(double); +}; + +void h(S(&&)[2]); // expected-note {{candidate function not viable}} +void h(S(&&)[3]); // expected-note {{candidate function not viable}} +void h(S(&&)[4]); // expected-note {{candidate function not viable}} + +void i() { + h({1}); + h({1, 2}); + h({1, 2, 3}); + h({1, 2, 3, 4}); + h({1, 2, 3, 4, 5}); // expected-error {{no matching function for call to 'h'}} +} + +} // namespace InitializerListToArray +#endif Index: clang/lib/Sema/SemaOverload.cpp =================================================================== --- clang/lib/Sema/SemaOverload.cpp +++ clang/lib/Sema/SemaOverload.cpp @@ -3686,6 +3686,100 @@ ICS.UserDefined.Before.DeprecatedStringLiteralToCharPtr); } +namespace { +class CompareListInitializationSequences { + // List-initialization sequence L1 is a better conversion sequence than + // list-initialization sequence L2 if: + // ... + // - C++17: + // L1 converts to type "array of N1 T", L2 converts to type "array of N2 T", + // and N1 is smaller than N2., + // - C++20 + // L1 and L2 convert to arrays of the same element type, and either the + // number of elements n1 initialized by L1 is less than the number of + // elements n2 initialized by L2, or n1=n2 and L2 converts to an array of + // unknown bound and L1 does not, + + struct Conversion { + enum ConversionKind { + Bad, + // Conversion to an array of known bound. The source doesn't have too + // many elements. + ConstantArray, + // Conversion to an array of unknown bound, requires C++20. + IncompleteArray + }; + ConversionKind Kind{Bad}; + // The number of elements the source has less than the target. + // - Zero means the source and target have the same number of elements, + // or the target is an array of unknown bound. + uint64_t Diff{0}; + QualType ElementType; + + Conversion(Sema &S, const ImplicitConversionSequence &ICS) { + const InitListExpr *From = ICS.getStdInitializerListFrom(); + QualType ToType = ICS.getStdInitializerListToType(); + + if (ToType->isIncompleteArrayType() && S.getLangOpts().CPlusPlus20) { + Kind = IncompleteArray; + ElementType = + S.Context.getAsIncompleteArrayType(ToType)->getElementType(); + + } else if (ToType->isConstantArrayType()) { + const auto *Array = S.Context.getAsConstantArrayType(ToType); + Diff = Array->getSize().getZExtValue(); + if (From->getNumInits() <= Diff) { + Diff -= From->getNumInits(); + Kind = ConstantArray; + } + ElementType = Array->getElementType(); + } + } + }; + + static ImplicitConversionSequence::CompareKind Compare(const Conversion &C1, + const Conversion &C2) { + if (C1.Kind != Conversion::Bad && C1.Kind == Conversion::Bad) + return ImplicitConversionSequence::Better; + + if (C1.Kind == Conversion::Bad && C1.Kind != Conversion::Bad) + return ImplicitConversionSequence::Worse; + + if (C1.Kind != Conversion::Bad && C1.Kind != Conversion::Bad) { + if (C1.Diff == 0 && C2.Diff == 0) { + if (C1.Kind == Conversion::ConstantArray && + C2.Kind == Conversion::IncompleteArray) + return ImplicitConversionSequence::Better; + if (C1.Kind == Conversion::IncompleteArray && + C2.Kind == Conversion::ConstantArray) + return ImplicitConversionSequence::Worse; + } else { + if (C1.Diff < C2.Diff) + return ImplicitConversionSequence::Better; + if (C1.Diff > C2.Diff) + return ImplicitConversionSequence::Worse; + } + } + + return ImplicitConversionSequence::Indistinguishable; + } + +public: + ImplicitConversionSequence::CompareKind Result{ + ImplicitConversionSequence::Indistinguishable}; + + CompareListInitializationSequences(Sema &S, + const ImplicitConversionSequence &ICS1, + const ImplicitConversionSequence &ICS2) { + Conversion C1(S, ICS1); + Conversion C2(S, ICS2); + if (!C1.ElementType.isNull() && !C2.ElementType.isNull() && + S.Context.hasSameType(C1.ElementType, C2.ElementType)) + Result = Compare(C1, C2); + } +}; +} // namespace + /// CompareImplicitConversionSequences - Compare two implicit /// conversion sequences to determine whether one is better than the /// other or if they are indistinguishable (C++ 13.3.3.2). @@ -3765,6 +3859,12 @@ if (!ICS1.isStdInitializerListElement() && ICS2.isStdInitializerListElement()) return ImplicitConversionSequence::Worse; + + if (ICS1.hasExtendedTypeInfo() && ICS2.hasExtendedTypeInfo()) { + CompareListInitializationSequences LIS(S, ICS1, ICS2); + if (LIS.Result != ImplicitConversionSequence::Indistinguishable) + return LIS.Result; + } } if (ICS1.isStandard()) @@ -5047,9 +5147,17 @@ Result.Standard.setAsIdentityConversion(); Result.Standard.setFromType(ToType); Result.Standard.setAllToTypes(ToType); + } else if (ToType->isConstantArrayType()) { + // Has the initializer list exactly N elements or fewer than N elements? + uint64_t Size = S.getASTContext() + .getAsConstantArrayType(ToType) + ->getSize() + .getZExtValue(); + if (From->getNumInits() > Size) + Result.setBad(BadConversionSequence::no_conversion, From, ToType); } - Result.setStdInitializerListElement(toStdInitializerList); + Result.setStdInitializerListElement(toStdInitializerList, From, ToType); return Result; } Index: clang/include/clang/Sema/Overload.h =================================================================== --- clang/include/clang/Sema/Overload.h +++ clang/include/clang/Sema/Overload.h @@ -539,6 +539,18 @@ /// sequence only represents the worst element conversion. unsigned StdInitializerListElement : 1; + /// Whether the target is used with an initializer list. + /// If so the rank algorithm needs to know about the number of elements + /// in the std::initializer_list or array. This information isn't stored in + /// the conversion sequences. + unsigned ExtendedTypeInfo : 1; + + /// The std::initializer_list expression to convert from. + const InitListExpr *StandardInitializerListFrom{nullptr}; + + /// The std::initializer_list or array to convert to. + void *StandardInitializerListToType{nullptr}; + void setKind(Kind K) { destruct(); ConversionKind = K; @@ -568,13 +580,17 @@ }; ImplicitConversionSequence() - : ConversionKind(Uninitialized), StdInitializerListElement(false) { + : ConversionKind(Uninitialized), StdInitializerListElement(false), + ExtendedTypeInfo(false) { Standard.setAsIdentityConversion(); } ImplicitConversionSequence(const ImplicitConversionSequence &Other) : ConversionKind(Other.ConversionKind), - StdInitializerListElement(Other.StdInitializerListElement) { + StdInitializerListElement(Other.StdInitializerListElement), + ExtendedTypeInfo(Other.ExtendedTypeInfo), + StandardInitializerListFrom(Other.StandardInitializerListFrom), + StandardInitializerListToType(Other.StandardInitializerListToType) { switch (ConversionKind) { case Uninitialized: break; case StandardConversion: Standard = Other.Standard; break; @@ -676,8 +692,25 @@ return StdInitializerListElement; } - void setStdInitializerListElement(bool V = true) { + void setStdInitializerListElement(bool V = true, + const InitListExpr *From = nullptr, + QualType ToType = QualType()) { StdInitializerListElement = V; + ExtendedTypeInfo = true; + StandardInitializerListFrom = From; + StandardInitializerListToType = ToType.getAsOpaquePtr(); + } + + bool hasExtendedTypeInfo() const { return ExtendedTypeInfo; } + + const InitListExpr *getStdInitializerListFrom() const { + assert(ExtendedTypeInfo); + return StandardInitializerListFrom; + } + + QualType getStdInitializerListToType() const { + assert(ExtendedTypeInfo); + return QualType::getFromOpaquePtr(StandardInitializerListToType); } /// Form an "implicit" conversion sequence from nullptr_t to bool, for a
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits