[clang] dcd7471 - [clang] p0388 conversion to incomplete array
Nathan Sidwell via cfe-commits
cfe-commits at lists.llvm.org
Tue Oct 12 07:35:40 PDT 2021
Author: Nathan Sidwell
Date: 2021-10-12T07:35:20-07:00
New Revision: dcd74716f9d18444a17de852a760cc85bd16f825
URL: https://github.com/llvm/llvm-project/commit/dcd74716f9d18444a17de852a760cc85bd16f825
DIFF: https://github.com/llvm/llvm-project/commit/dcd74716f9d18444a17de852a760cc85bd16f825.diff
LOG: [clang] p0388 conversion to incomplete array
This implements the new implicit conversion sequence to an incomplete
(unbounded) array type. It is mostly Richard Smith's work, updated to
trunk, testcases added and a few bugs fixed found in such testing.
It is not a complete implementation of p0388.
Differential Revision: https://reviews.llvm.org/D102645
Added:
clang/test/CodeGenCXX/cxx20-p0388-unbound-ary.cpp
clang/test/SemaCXX/cxx20-p0388-unbound-ary.cpp
Modified:
clang/include/clang/AST/ASTContext.h
clang/lib/AST/ASTContext.cpp
clang/lib/CodeGen/CGExpr.cpp
clang/lib/CodeGen/CGExprScalar.cpp
clang/lib/CodeGen/CGObjC.cpp
clang/lib/Sema/SemaCast.cpp
clang/lib/Sema/SemaExprCXX.cpp
clang/lib/Sema/SemaOverload.cpp
clang/test/CXX/drs/dr3xx.cpp
Removed:
################################################################################
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index 443df3790fc13..b21f9b83b9d86 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -2535,8 +2535,10 @@ class ASTContext : public RefCountedBase<ASTContext> {
bool ObjCMethodsAreEqual(const ObjCMethodDecl *MethodDecl,
const ObjCMethodDecl *MethodImp);
- bool UnwrapSimilarTypes(QualType &T1, QualType &T2);
- void UnwrapSimilarArrayTypes(QualType &T1, QualType &T2);
+ bool UnwrapSimilarTypes(QualType &T1, QualType &T2,
+ bool AllowPiMismatch = true);
+ void UnwrapSimilarArrayTypes(QualType &T1, QualType &T2,
+ bool AllowPiMismatch = true);
/// Determine if two types are similar, according to the C++ rules. That is,
/// determine if they are the same other than qualifiers on the initial
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index f4239869e93db..da49084f518a3 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -5844,7 +5844,11 @@ QualType ASTContext::getUnqualifiedArrayType(QualType type,
/// Attempt to unwrap two types that may both be array types with the same bound
/// (or both be array types of unknown bound) for the purpose of comparing the
/// cv-decomposition of two types per C++ [conv.qual].
-void ASTContext::UnwrapSimilarArrayTypes(QualType &T1, QualType &T2) {
+///
+/// \param AllowPiMismatch Allow the Pi1 and Pi2 to
diff er as described in
+/// C++20 [conv.qual], if permitted by the current language mode.
+void ASTContext::UnwrapSimilarArrayTypes(QualType &T1, QualType &T2,
+ bool AllowPiMismatch) {
while (true) {
auto *AT1 = getAsArrayType(T1);
if (!AT1)
@@ -5856,12 +5860,21 @@ void ASTContext::UnwrapSimilarArrayTypes(QualType &T1, QualType &T2) {
// If we don't have two array types with the same constant bound nor two
// incomplete array types, we've unwrapped everything we can.
+ // C++20 also permits one type to be a constant array type and the other
+ // to be an incomplete array type.
+ // FIXME: Consider also unwrapping array of unknown bound and VLA.
if (auto *CAT1 = dyn_cast<ConstantArrayType>(AT1)) {
auto *CAT2 = dyn_cast<ConstantArrayType>(AT2);
- if (!CAT2 || CAT1->getSize() != CAT2->getSize())
+ if (!(CAT2 && CAT1->getSize() == CAT2->getSize()) &&
+ !(getLangOpts().CPlusPlus20 && AllowPiMismatch &&
+ isa<IncompleteArrayType>(AT2)))
+ return;
+ } else if (isa<IncompleteArrayType>(AT1)) {
+ if (!isa<IncompleteArrayType>(AT2) &&
+ !(getLangOpts().CPlusPlus20 && AllowPiMismatch &&
+ isa<ConstantArrayType>(AT2)))
return;
- } else if (!isa<IncompleteArrayType>(AT1) ||
- !isa<IncompleteArrayType>(AT2)) {
+ } else {
return;
}
@@ -5880,10 +5893,14 @@ void ASTContext::UnwrapSimilarArrayTypes(QualType &T1, QualType &T2) {
/// "unwraps" pointer and pointer-to-member types to compare them at each
/// level.
///
+/// \param AllowPiMismatch Allow the Pi1 and Pi2 to
diff er as described in
+/// C++20 [conv.qual], if permitted by the current language mode.
+///
/// \return \c true if a pointer type was unwrapped, \c false if we reached a
/// pair of types that can't be unwrapped further.
-bool ASTContext::UnwrapSimilarTypes(QualType &T1, QualType &T2) {
- UnwrapSimilarArrayTypes(T1, T2);
+bool ASTContext::UnwrapSimilarTypes(QualType &T1, QualType &T2,
+ bool AllowPiMismatch) {
+ UnwrapSimilarArrayTypes(T1, T2, AllowPiMismatch);
const auto *T1PtrType = T1->getAs<PointerType>();
const auto *T2PtrType = T2->getAs<PointerType>();
@@ -5944,7 +5961,7 @@ bool ASTContext::hasCvrSimilarType(QualType T1, QualType T2) {
if (hasSameType(T1, T2))
return true;
- if (!UnwrapSimilarTypes(T1, T2))
+ if (!UnwrapSimilarTypes(T1, T2, /*AllowPiMismatch*/ false))
return false;
}
}
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 2daf492d60662..e73782f2d317c 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -4683,10 +4683,28 @@ LValue CodeGenFunction::EmitCastLValue(const CastExpr *E) {
case CK_UserDefinedConversion:
case CK_CPointerToObjCPointerCast:
case CK_BlockPointerToObjCPointerCast:
- case CK_NoOp:
case CK_LValueToRValue:
return EmitLValue(E->getSubExpr());
+ case CK_NoOp: {
+ // CK_NoOp can model a qualification conversion, which can remove an array
+ // bound and change the IR type.
+ // FIXME: Once pointee types are removed from IR, remove this.
+ LValue LV = EmitLValue(E->getSubExpr());
+ if (LV.isSimple()) {
+ Address V = LV.getAddress(*this);
+ if (V.isValid()) {
+ llvm::Type *T =
+ ConvertTypeForMem(E->getType())
+ ->getPointerTo(
+ cast<llvm::PointerType>(V.getType())->getAddressSpace());
+ if (V.getType() != T)
+ LV.setAddress(Builder.CreateBitCast(V, T));
+ }
+ }
+ return LV;
+ }
+
case CK_UncheckedDerivedToBase:
case CK_DerivedToBase: {
const auto *DerivedClassTy =
diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp
index ed485ed6f5d15..866da292819f8 100644
--- a/clang/lib/CodeGen/CGExprScalar.cpp
+++ b/clang/lib/CodeGen/CGExprScalar.cpp
@@ -2154,10 +2154,22 @@ Value *ScalarExprEmitter::VisitCastExpr(CastExpr *CE) {
}
case CK_AtomicToNonAtomic:
case CK_NonAtomicToAtomic:
- case CK_NoOp:
case CK_UserDefinedConversion:
return Visit(const_cast<Expr*>(E));
+ case CK_NoOp: {
+ llvm::Value *V = Visit(const_cast<Expr *>(E));
+ if (V) {
+ // CK_NoOp can model a pointer qualification conversion, which can remove
+ // an array bound and change the IR type.
+ // FIXME: Once pointee types are removed from IR, remove this.
+ llvm::Type *T = ConvertType(DestTy);
+ if (T != V->getType())
+ V = Builder.CreateBitCast(V, T);
+ }
+ return V;
+ }
+
case CK_BaseToDerived: {
const CXXRecordDecl *DerivedClassDecl = DestTy->getPointeeCXXRecordDecl();
assert(DerivedClassDecl && "BaseToDerived arg isn't a C++ object pointer!");
diff --git a/clang/lib/CodeGen/CGObjC.cpp b/clang/lib/CodeGen/CGObjC.cpp
index 4eced4ec046fb..b16502e7a29d4 100644
--- a/clang/lib/CodeGen/CGObjC.cpp
+++ b/clang/lib/CodeGen/CGObjC.cpp
@@ -1555,6 +1555,12 @@ CodeGenFunction::generateObjCSetterBody(const ObjCImplementationDecl *classImpl,
argCK = CK_AnyPointerToBlockPointerCast;
} else if (ivarRef.getType()->isPointerType()) {
argCK = CK_BitCast;
+ } else if (argLoad.getType()->isAtomicType() &&
+ !ivarRef.getType()->isAtomicType()) {
+ argCK = CK_AtomicToNonAtomic;
+ } else if (!argLoad.getType()->isAtomicType() &&
+ ivarRef.getType()->isAtomicType()) {
+ argCK = CK_NonAtomicToAtomic;
}
ImplicitCastExpr argCast(ImplicitCastExpr::OnStack, ivarRef.getType(), argCK,
&argLoad, VK_PRValue, FPOptionsOverride());
diff --git a/clang/lib/Sema/SemaCast.cpp b/clang/lib/Sema/SemaCast.cpp
index 50edcc1c23020..7ef1732496c21 100644
--- a/clang/lib/Sema/SemaCast.cpp
+++ b/clang/lib/Sema/SemaCast.cpp
@@ -1313,7 +1313,9 @@ static TryCastResult TryStaticCast(Sema &Self, ExprResult &SrcExpr,
// lvalue-to-rvalue, array-to-pointer, function-to-pointer, and boolean
// conversions, subject to further restrictions.
// Also, C++ 5.2.9p1 forbids casting away constness, which makes reversal
- // of qualification conversions impossible.
+ // of qualification conversions impossible. (In C++20, adding an array bound
+ // would be the reverse of a qualification conversion, but adding permission
+ // to add an array bound in a static_cast is a wording oversight.)
// In the CStyle case, the earlier attempt to const_cast should have taken
// care of reverse qualification conversions.
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index 8e2a549c7a9c3..6b091364c911f 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -6711,6 +6711,36 @@ QualType Sema::FindCompositePointerType(SourceLocation Loc,
}
// FIXME: Can we unify the following with UnwrapSimilarTypes?
+
+ const ArrayType *Arr1, *Arr2;
+ if ((Arr1 = Context.getAsArrayType(Composite1)) &&
+ (Arr2 = Context.getAsArrayType(Composite2))) {
+ auto *CAT1 = dyn_cast<ConstantArrayType>(Arr1);
+ auto *CAT2 = dyn_cast<ConstantArrayType>(Arr2);
+ if (CAT1 && CAT2 && CAT1->getSize() == CAT2->getSize()) {
+ Composite1 = Arr1->getElementType();
+ Composite2 = Arr2->getElementType();
+ Steps.emplace_back(Step::Array, CAT1);
+ continue;
+ }
+ bool IAT1 = isa<IncompleteArrayType>(Arr1);
+ bool IAT2 = isa<IncompleteArrayType>(Arr2);
+ if ((IAT1 && IAT2) ||
+ (getLangOpts().CPlusPlus20 && (IAT1 != IAT2) &&
+ ((bool)CAT1 != (bool)CAT2) &&
+ (Steps.empty() || Steps.back().K != Step::Array))) {
+ // In C++20 onwards, we can unify an array of N T with an array of
+ // a
diff erent or unknown bound. But we can't form an array whose
+ // element type is an array of unknown bound by doing so.
+ Composite1 = Arr1->getElementType();
+ Composite2 = Arr2->getElementType();
+ Steps.emplace_back(Step::Array);
+ if (CAT1 || CAT2)
+ NeedConstBefore = Steps.size();
+ continue;
+ }
+ }
+
const PointerType *Ptr1, *Ptr2;
if ((Ptr1 = Composite1->getAs<PointerType>()) &&
(Ptr2 = Composite2->getAs<PointerType>())) {
@@ -6771,8 +6801,6 @@ QualType Sema::FindCompositePointerType(SourceLocation Loc,
continue;
}
- // FIXME: arrays
-
// FIXME: block pointer types?
// Cannot unwrap any more types.
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index e12413ca994f4..5c513a4326933 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -3246,6 +3246,19 @@ static bool isQualificationConversionStep(QualType FromType, QualType ToType,
!PreviousToQualsIncludeConst)
return false;
+ // The following wording is from C++20, where the result of the conversion
+ // is T3, not T2.
+ // -- if [...] P1,i [...] is "array of unknown bound of", P3,i is
+ // "array of unknown bound of"
+ if (FromType->isIncompleteArrayType() && !ToType->isIncompleteArrayType())
+ return false;
+
+ // -- if the resulting P3,i is
diff erent from P1,i [...], then const is
+ // added to every cv 3_k for 0 < k < i.
+ if (!CStyle && FromType->isConstantArrayType() &&
+ ToType->isIncompleteArrayType() && !PreviousToQualsIncludeConst)
+ return false;
+
// Keep track of whether all prior cv-qualifiers in the "to" type
// include const.
PreviousToQualsIncludeConst =
@@ -4199,12 +4212,15 @@ static ImplicitConversionSequence::CompareKind
CompareQualificationConversions(Sema &S,
const StandardConversionSequence& SCS1,
const StandardConversionSequence& SCS2) {
- // C++ 13.3.3.2p3:
+ // C++ [over.ics.rank]p3:
// -- S1 and S2
diff er only in their qualification conversion and
- // yield similar types T1 and T2 (C++ 4.4), respectively, and the
- // cv-qualification signature of type T1 is a proper subset of
- // the cv-qualification signature of type T2, and S1 is not the
+ // yield similar types T1 and T2 (C++ 4.4), respectively, [...]
+ // [C++98]
+ // [...] and the cv-qualification signature of type T1 is a proper subset
+ // of the cv-qualification signature of type T2, and S1 is not the
// deprecated string literal array-to-pointer conversion (4.2).
+ // [C++2a]
+ // [...] where T1 can be converted to T2 by a qualification conversion.
if (SCS1.First != SCS2.First || SCS1.Second != SCS2.Second ||
SCS1.Third != SCS2.Third || SCS1.Third != ICK_Qualification)
return ImplicitConversionSequence::Indistinguishable;
@@ -4225,79 +4241,35 @@ CompareQualificationConversions(Sema &S,
if (UnqualT1 == UnqualT2)
return ImplicitConversionSequence::Indistinguishable;
- ImplicitConversionSequence::CompareKind Result
- = ImplicitConversionSequence::Indistinguishable;
+ // Don't ever prefer a standard conversion sequence that uses the deprecated
+ // string literal array to pointer conversion.
+ bool CanPick1 = !SCS1.DeprecatedStringLiteralToCharPtr;
+ bool CanPick2 = !SCS2.DeprecatedStringLiteralToCharPtr;
// Objective-C++ ARC:
// Prefer qualification conversions not involving a change in lifetime
- // to qualification conversions that do not change lifetime.
- if (SCS1.QualificationIncludesObjCLifetime !=
- SCS2.QualificationIncludesObjCLifetime) {
- Result = SCS1.QualificationIncludesObjCLifetime
- ? ImplicitConversionSequence::Worse
- : ImplicitConversionSequence::Better;
- }
-
- while (S.Context.UnwrapSimilarTypes(T1, T2)) {
- // Within each iteration of the loop, we check the qualifiers to
- // determine if this still looks like a qualification
- // conversion. Then, if all is well, we unwrap one more level of
- // pointers or pointers-to-members and do it all again
- // until there are no more pointers or pointers-to-members left
- // to unwrap. This essentially mimics what
- // IsQualificationConversion does, but here we're checking for a
- // strict subset of qualifiers.
- if (T1.getQualifiers().withoutObjCLifetime() ==
- T2.getQualifiers().withoutObjCLifetime())
- // The qualifiers are the same, so this doesn't tell us anything
- // about how the sequences rank.
- // ObjC ownership quals are omitted above as they interfere with
- // the ARC overload rule.
- ;
- else if (T2.isMoreQualifiedThan(T1)) {
- // T1 has fewer qualifiers, so it could be the better sequence.
- if (Result == ImplicitConversionSequence::Worse)
- // Neither has qualifiers that are a subset of the other's
- // qualifiers.
- return ImplicitConversionSequence::Indistinguishable;
-
- Result = ImplicitConversionSequence::Better;
- } else if (T1.isMoreQualifiedThan(T2)) {
- // T2 has fewer qualifiers, so it could be the better sequence.
- if (Result == ImplicitConversionSequence::Better)
- // Neither has qualifiers that are a subset of the other's
- // qualifiers.
- return ImplicitConversionSequence::Indistinguishable;
-
- Result = ImplicitConversionSequence::Worse;
- } else {
- // Qualifiers are disjoint.
- return ImplicitConversionSequence::Indistinguishable;
- }
-
- // If the types after this point are equivalent, we're done.
- if (S.Context.hasSameUnqualifiedType(T1, T2))
- break;
- }
-
- // Check that the winning standard conversion sequence isn't using
- // the deprecated string literal array to pointer conversion.
- switch (Result) {
- case ImplicitConversionSequence::Better:
- if (SCS1.DeprecatedStringLiteralToCharPtr)
- Result = ImplicitConversionSequence::Indistinguishable;
- break;
+ // to qualification conversions that do change lifetime.
+ if (SCS1.QualificationIncludesObjCLifetime &&
+ !SCS2.QualificationIncludesObjCLifetime)
+ CanPick1 = false;
+ if (SCS2.QualificationIncludesObjCLifetime &&
+ !SCS1.QualificationIncludesObjCLifetime)
+ CanPick2 = false;
- case ImplicitConversionSequence::Indistinguishable:
- break;
-
- case ImplicitConversionSequence::Worse:
- if (SCS2.DeprecatedStringLiteralToCharPtr)
- Result = ImplicitConversionSequence::Indistinguishable;
- break;
- }
-
- return Result;
+ bool ObjCLifetimeConversion;
+ if (CanPick1 &&
+ !S.IsQualificationConversion(T1, T2, false, ObjCLifetimeConversion))
+ CanPick1 = false;
+ // FIXME: In Objective-C ARC, we can have qualification conversions in both
+ // directions, so we can't short-cut this second check in general.
+ if (CanPick2 &&
+ !S.IsQualificationConversion(T2, T1, false, ObjCLifetimeConversion))
+ CanPick2 = false;
+
+ if (CanPick1 != CanPick2)
+ return CanPick1 ? ImplicitConversionSequence::Better
+ : ImplicitConversionSequence::Worse;
+ return ImplicitConversionSequence::Indistinguishable;
}
/// CompareDerivedToBaseConversions - Compares two standard conversion
diff --git a/clang/test/CXX/drs/dr3xx.cpp b/clang/test/CXX/drs/dr3xx.cpp
index 9c8fe2de2f598..25dc207717f67 100644
--- a/clang/test/CXX/drs/dr3xx.cpp
+++ b/clang/test/CXX/drs/dr3xx.cpp
@@ -373,10 +373,19 @@ namespace dr330 { // dr330: 7
q = p; // ok
q2 = p; // ok
r = p; // expected-error {{incompatible}}
- s = p; // expected-error {{incompatible}} (for now)
+ s = p;
+#if __cplusplus < 202002
+ // expected-error at -2 {{incompatible}} (fixed by p0388)
+#endif
t = p; // expected-error {{incompatible}}
- s = q; // expected-error {{incompatible}}
- s = q2; // expected-error {{incompatible}}
+ s = q;
+#if __cplusplus < 202002
+ // expected-error at -2 {{incompatible}} (fixed by p0388)
+#endif
+ s = q2;
+#if __cplusplus < 202002
+ // expected-error at -2 {{incompatible}} (fixed by p0388)
+#endif
s = t; // ok, adding const
t = s; // expected-error {{discards qualifiers}}
(void) const_cast<P>(q);
diff --git a/clang/test/CodeGenCXX/cxx20-p0388-unbound-ary.cpp b/clang/test/CodeGenCXX/cxx20-p0388-unbound-ary.cpp
new file mode 100644
index 0000000000000..3d50c0edc68ba
--- /dev/null
+++ b/clang/test/CodeGenCXX/cxx20-p0388-unbound-ary.cpp
@@ -0,0 +1,27 @@
+// RUN: %clang_cc1 %s -triple %itanium_abi_triple -std=c++20 -emit-llvm -O2 -o - | FileCheck %s
+
+// p0388 conversions to unbounded array
+// dcl.init.list/3
+
+namespace One {
+int ga[1];
+
+// CHECK-LABEL: @_ZN3One5frob1Ev
+// CHECK-NEXT: entry:
+// CHECK-NEXT: ret [0 x i32]* bitcast ([1 x i32]* @_ZN3One2gaE to [0 x i32]*)
+auto &frob1() {
+ int(&r1)[] = ga;
+
+ return r1;
+}
+
+// CHECK-LABEL: @_ZN3One5frob2ERA1_i
+// CHECK-NEXT: entry:
+// CHECK-NEXT: %0 = bitcast [1 x i32]* %arp to [0 x i32]*
+// CHECK-NEXT: ret [0 x i32]* %0
+auto &frob2(int (&arp)[1]) {
+ int(&r2)[] = arp;
+
+ return r2;
+}
+} // namespace One
diff --git a/clang/test/SemaCXX/cxx20-p0388-unbound-ary.cpp b/clang/test/SemaCXX/cxx20-p0388-unbound-ary.cpp
new file mode 100644
index 0000000000000..34f0869382b4d
--- /dev/null
+++ b/clang/test/SemaCXX/cxx20-p0388-unbound-ary.cpp
@@ -0,0 +1,79 @@
+// RUN: %clang_cc1 -std=c++20 -verify %s
+// RUN: %clang_cc1 -std=c++17 -verify %s
+
+// p0388 conversions to unbounded array
+// dcl.init.list/3
+
+namespace One {
+int ga[1];
+
+auto &frob1() {
+ int(&r1)[] = ga;
+#if __cplusplus < 202002
+ // expected-error at -2{{cannot bind to a value of unrelated type}}
+#endif
+
+ return r1;
+}
+
+auto &frob2(int (&arp)[1]) {
+ int(&r2)[] = arp;
+#if __cplusplus < 202002
+ // expected-error at -2{{cannot bind to a value of unrelated type}}
+#endif
+
+ return r2;
+}
+} // namespace One
+
+namespace Two {
+int ga[1];
+
+auto *frob1() {
+ int(*r1)[] = &ga;
+#if __cplusplus < 202002
+ // expected-error at -2{{with an rvalue of type}}
+#endif
+
+ return r1;
+}
+
+auto *frob2(int (*arp)[1]) {
+ int(*r2)[] = arp;
+#if __cplusplus < 202002
+ // expected-error at -2{{with an lvalue of type}}
+#endif
+
+ return r2;
+}
+} // namespace Two
+
+namespace Four {
+using Inc = int[2];
+using Mat = Inc[1];
+Mat *ga[2];
+
+auto *frob1() {
+ Inc(*const(*r1)[])[] = &ga;
+#if __cplusplus < 202002
+ // expected-error at -2{{with an rvalue of type}}
+#else
+ // missing a required 'const'
+ Inc(*(*r2)[])[] = &ga; // expected-error{{cannot initialize}}
+#endif
+
+ return r1;
+}
+
+auto *frob2(Mat *(*arp)[1]) {
+ Inc(*const(*r2)[])[] = arp;
+#if __cplusplus < 202002
+ // expected-error at -2{{with an lvalue of type}}
+#else
+ Inc(*(*r3)[])[] = arp; // expected-error{{cannot initialize}}
+#endif
+
+ return r2;
+}
+
+} // namespace Four
More information about the cfe-commits
mailing list