[clang] [libcxx] [Clang] Add __builtin_invoke and use it in libc++ (PR #116709)
via cfe-commits
cfe-commits at lists.llvm.org
Tue May 20 05:17:31 PDT 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-libcxx
Author: Nikolas Klauser (philnik777)
<details>
<summary>Changes</summary>
`std::invoke` is currently quite heavy compared to a function call, since it involves quite heavy SFINAE. This can be done significantly more efficient by the compiler, since most calls to `std::invoke` are simple function calls and 6 out of the seven overloads for `std::invoke` exist only to support member pointers. Even these boil down to a few relatively simple checks.
Some real-world testing with this patch revealed some significant results. For example, instantiating `std::format("Banane")` (and its callees) went down from ~125ms on my system to ~104ms.
---
Patch is 29.32 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/116709.diff
9 Files Affected:
- (modified) clang/include/clang/Basic/Builtins.td (+6)
- (modified) clang/include/clang/Sema/Sema.h (+9)
- (modified) clang/lib/Parse/ParseDeclCXX.cpp (+1-23)
- (modified) clang/lib/Sema/SemaChecking.cpp (+97)
- (modified) clang/lib/Sema/SemaExprCXX.cpp (+54-51)
- (added) clang/test/CodeGenCXX/builtin-invoke.cpp (+61)
- (added) clang/test/SemaCXX/builtin-invoke.cpp (+133)
- (modified) libcxx/include/__type_traits/invoke.h (+127-28)
- (modified) libcxx/include/__type_traits/is_core_convertible.h (+11)
``````````diff
diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td
index 187d3b5ed24a7..58cc35088c40a 100644
--- a/clang/include/clang/Basic/Builtins.td
+++ b/clang/include/clang/Basic/Builtins.td
@@ -4272,6 +4272,12 @@ def MoveIfNsoexcept : CxxLibBuiltin<"utility"> {
let Namespace = "std";
}
+def Invoke : Builtin {
+ let Spellings = ["__builtin_invoke"];
+ let Attributes = [CustomTypeChecking, Constexpr];
+ let Prototype = "void(...)";
+}
+
def Annotation : Builtin {
let Spellings = ["__builtin_annotation"];
let Attributes = [NoThrow, CustomTypeChecking];
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 5ec67087aeea4..22d66e8688906 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2594,6 +2594,8 @@ class Sema final : public SemaBase {
SourceLocation BuiltinLoc,
SourceLocation RParenLoc);
+ ExprResult BuiltinInvoke(CallExpr *TheCall);
+
static StringRef GetFormatStringTypeName(FormatStringType FST);
static FormatStringType GetFormatStringType(StringRef FormatFlavor);
static FormatStringType GetFormatStringType(const FormatAttr *Format);
@@ -15220,11 +15222,18 @@ class Sema final : public SemaBase {
SourceLocation Loc);
QualType BuiltinRemoveReference(QualType BaseType, UTTKind UKind,
SourceLocation Loc);
+
+ QualType BuiltinRemoveCVRef(QualType BaseType, SourceLocation Loc) {
+ return BuiltinRemoveReference(BaseType, UTTKind::RemoveCVRef, Loc);
+ }
+
QualType BuiltinChangeCVRQualifiers(QualType BaseType, UTTKind UKind,
SourceLocation Loc);
QualType BuiltinChangeSignedness(QualType BaseType, UTTKind UKind,
SourceLocation Loc);
+ bool BuiltinIsBaseOf(SourceLocation RhsTLoc, QualType LhsT, QualType RhsT);
+
/// Ensure that the type T is a literal type.
///
/// This routine checks whether the type @p T is a literal type. If @p T is an
diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp
index 316bc30edf1f0..aeb1112bad8b4 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -1611,29 +1611,7 @@ void Parser::ParseClassSpecifier(tok::TokenKind TagTokKind,
Tok.isOneOf(
#define TRANSFORM_TYPE_TRAIT_DEF(_, Trait) tok::kw___##Trait,
#include "clang/Basic/TransformTypeTraits.def"
- tok::kw___is_abstract,
- tok::kw___is_aggregate,
- tok::kw___is_arithmetic,
- tok::kw___is_array,
- tok::kw___is_assignable,
- tok::kw___is_base_of,
- tok::kw___is_bounded_array,
- tok::kw___is_class,
- tok::kw___is_complete_type,
- tok::kw___is_compound,
- tok::kw___is_const,
- tok::kw___is_constructible,
- tok::kw___is_convertible,
- tok::kw___is_convertible_to,
- tok::kw___is_destructible,
- tok::kw___is_empty,
- tok::kw___is_enum,
- tok::kw___is_floating_point,
- tok::kw___is_final,
- tok::kw___is_function,
- tok::kw___is_fundamental,
- tok::kw___is_integral,
- tok::kw___is_interface_class,
+ tok::kw___is_convertible, // Last use in libc++ was removed in 925a11a
tok::kw___is_literal,
tok::kw___is_lvalue_expr,
tok::kw___is_lvalue_reference,
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index a960b9931ddfd..26579de25bdf0 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -2368,6 +2368,8 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
return BuiltinShuffleVector(TheCall);
// TheCall will be freed by the smart pointer here, but that's fine, since
// BuiltinShuffleVector guts it, but then doesn't release it.
+ case Builtin::BI__builtin_invoke:
+ return BuiltinInvoke(TheCall);
case Builtin::BI__builtin_prefetch:
if (BuiltinPrefetch(TheCall))
return ExprError();
@@ -5406,6 +5408,101 @@ ExprResult Sema::ConvertVectorExpr(Expr *E, TypeSourceInfo *TInfo,
RParenLoc, CurFPFeatureOverrides());
}
+ExprResult Sema::BuiltinInvoke(CallExpr *TheCall) {
+ auto Loc = TheCall->getBeginLoc();
+ auto Args = MutableArrayRef(TheCall->getArgs(), TheCall->getNumArgs());
+ assert(llvm::none_of(Args,
+ [](Expr *Arg) { return Arg->isTypeDependent(); }));
+
+ if (Args.size() == 0) {
+ Diag(TheCall->getBeginLoc(), diag::err_typecheck_call_too_few_args_at_least)
+ << 0 << 1 << 0 << 0 << TheCall->getSourceRange();
+ return ExprError();
+ }
+
+ auto FuncT = Args[0]->getType();
+
+ if (auto *MPT = FuncT->getAs<MemberPointerType>()) {
+ if (Args.size() < 2) {
+ Diag(TheCall->getBeginLoc(),
+ diag::err_typecheck_call_too_few_args_at_least)
+ << 0 << 2 << 1 << 0 << TheCall->getSourceRange();
+ return ExprError();
+ }
+
+ auto *MemPtrClass = MPT->getQualifier()->getAsType();
+ auto ObjectT = Args[1]->getType();
+
+
+ if (MPT->isMemberDataPointer() && Args.size() != 2) {
+ Diag(TheCall->getBeginLoc(), diag::err_typecheck_call_too_many_args)
+ << 0 << 2 << Args.size() << 0 << TheCall->getSourceRange();
+ return ExprError();
+ }
+
+ ExprResult ObjectArg = [&]() -> ExprResult {
+ // (1.1): (t1.*f)(t2, …, tN) when f is a pointer to a member function of a
+ // class T and is_same_v<T, remove_cvref_t<decltype(t1)>> ||
+ // is_base_of_v<T, remove_cvref_t<decltype(t1)>> is true;
+ // (1.4): t1.*f when N=1 and f is a pointer to data member of a class T
+ // and is_same_v<T, remove_cvref_t<decltype(t1)>> ||
+ // is_base_of_v<T, remove_cvref_t<decltype(t1)>> is true;
+ if (Context.hasSameType(QualType(MemPtrClass, 0),
+ BuiltinRemoveCVRef(ObjectT, Loc)) ||
+ BuiltinIsBaseOf(Args[1]->getBeginLoc(), QualType(MemPtrClass, 0),
+ BuiltinRemoveCVRef(ObjectT, Loc))) {
+ return Args[1];
+ }
+
+ // (t1.get().*f)(t2, …, tN) when f is a pointer to a member function of
+ // a class T and remove_cvref_t<decltype(t1)> is a specialization of
+ // reference_wrapper;
+ if (auto *RD = ObjectT->getAsCXXRecordDecl()) {
+ if (RD->isInStdNamespace() &&
+ RD->getDeclName().getAsString() == "reference_wrapper") {
+ CXXScopeSpec SS;
+ IdentifierInfo *GetName = &Context.Idents.get("get");
+ UnqualifiedId GetID;
+ GetID.setIdentifier(GetName, Loc);
+
+ auto MemExpr = ActOnMemberAccessExpr(
+ getCurScope(), Args[1], Loc, tok::period, SS,
+ /*TemplateKWLoc=*/SourceLocation(), GetID, nullptr);
+
+ if (MemExpr.isInvalid())
+ return ExprError();
+
+ return ActOnCallExpr(getCurScope(), MemExpr.get(), Loc, {}, Loc);
+ }
+ }
+
+ // ((*t1).*f)(t2, …, tN) when f is a pointer to a member function of a
+ // class T and t1 does not satisfy the previous two items;
+
+ return ActOnUnaryOp(getCurScope(), Loc, tok::star, Args[1]);
+ }();
+
+ if (ObjectArg.isInvalid())
+ return ExprError();
+
+ auto BinOp = ActOnBinOp(getCurScope(), TheCall->getBeginLoc(),
+ tok::periodstar, ObjectArg.get(), Args[0]);
+ if (BinOp.isInvalid())
+ return ExprError();
+
+ if (MPT->isMemberDataPointer())
+ return BinOp;
+
+ auto *MemCall = new (Context)
+ ParenExpr(SourceLocation(), SourceLocation(), BinOp.get());
+
+ return ActOnCallExpr(getCurScope(), MemCall, TheCall->getBeginLoc(),
+ Args.drop_front(2), TheCall->getRParenLoc());
+ }
+ return ActOnCallExpr(getCurScope(), Args.front(), TheCall->getBeginLoc(),
+ Args.drop_front(), TheCall->getRParenLoc());
+}
+
bool Sema::BuiltinPrefetch(CallExpr *TheCall) {
unsigned NumArgs = TheCall->getNumArgs();
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index b071c98051bbe..e945b446953c7 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -6540,67 +6540,70 @@ ExprResult Sema::ActOnTypeTrait(TypeTrait Kind, SourceLocation KWLoc,
return BuildTypeTrait(Kind, KWLoc, ConvertedArgs, RParenLoc);
}
-static bool EvaluateBinaryTypeTrait(Sema &Self, TypeTrait BTT, const TypeSourceInfo *Lhs,
- const TypeSourceInfo *Rhs, SourceLocation KeyLoc) {
- QualType LhsT = Lhs->getType();
- QualType RhsT = Rhs->getType();
+bool Sema::BuiltinIsBaseOf(SourceLocation RhsTLoc, QualType LhsT,
+ QualType RhsT) {
+ // C++0x [meta.rel]p2
+ // Base is a base class of Derived without regard to cv-qualifiers or
+ // Base and Derived are not unions and name the same class type without
+ // regard to cv-qualifiers.
+
+ const RecordType *lhsRecord = LhsT->getAs<RecordType>();
+ const RecordType *rhsRecord = RhsT->getAs<RecordType>();
+ if (!rhsRecord || !lhsRecord) {
+ const ObjCObjectType *LHSObjTy = LhsT->getAs<ObjCObjectType>();
+ const ObjCObjectType *RHSObjTy = RhsT->getAs<ObjCObjectType>();
+ if (!LHSObjTy || !RHSObjTy)
+ return false;
- assert(!LhsT->isDependentType() && !RhsT->isDependentType() &&
- "Cannot evaluate traits of dependent types");
+ ObjCInterfaceDecl *BaseInterface = LHSObjTy->getInterface();
+ ObjCInterfaceDecl *DerivedInterface = RHSObjTy->getInterface();
+ if (!BaseInterface || !DerivedInterface)
+ return false;
- switch(BTT) {
- case BTT_IsBaseOf: {
- // C++0x [meta.rel]p2
- // Base is a base class of Derived without regard to cv-qualifiers or
- // Base and Derived are not unions and name the same class type without
- // regard to cv-qualifiers.
-
- const RecordType *lhsRecord = LhsT->getAs<RecordType>();
- const RecordType *rhsRecord = RhsT->getAs<RecordType>();
- if (!rhsRecord || !lhsRecord) {
- const ObjCObjectType *LHSObjTy = LhsT->getAs<ObjCObjectType>();
- const ObjCObjectType *RHSObjTy = RhsT->getAs<ObjCObjectType>();
- if (!LHSObjTy || !RHSObjTy)
- return false;
+ if (RequireCompleteType(RhsTLoc, RhsT,
+ diag::err_incomplete_type_used_in_type_trait_expr))
+ return false;
- ObjCInterfaceDecl *BaseInterface = LHSObjTy->getInterface();
- ObjCInterfaceDecl *DerivedInterface = RHSObjTy->getInterface();
- if (!BaseInterface || !DerivedInterface)
- return false;
+ return BaseInterface->isSuperClassOf(DerivedInterface);
+ }
- if (Self.RequireCompleteType(
- Rhs->getTypeLoc().getBeginLoc(), RhsT,
- diag::err_incomplete_type_used_in_type_trait_expr))
- return false;
+ assert(Context.hasSameUnqualifiedType(LhsT, RhsT) ==
+ (lhsRecord == rhsRecord));
- return BaseInterface->isSuperClassOf(DerivedInterface);
- }
+ // Unions are never base classes, and never have base classes.
+ // It doesn't matter if they are complete or not. See PR#41843
+ if (lhsRecord && lhsRecord->getDecl()->isUnion())
+ return false;
+ if (rhsRecord && rhsRecord->getDecl()->isUnion())
+ return false;
+
+ if (lhsRecord == rhsRecord)
+ return true;
- assert(Self.Context.hasSameUnqualifiedType(LhsT, RhsT)
- == (lhsRecord == rhsRecord));
+ // C++0x [meta.rel]p2:
+ // If Base and Derived are class types and are different types
+ // (ignoring possible cv-qualifiers) then Derived shall be a
+ // complete type.
+ if (RequireCompleteType(RhsTLoc, RhsT,
+ diag::err_incomplete_type_used_in_type_trait_expr))
+ return false;
- // Unions are never base classes, and never have base classes.
- // It doesn't matter if they are complete or not. See PR#41843
- if (lhsRecord && lhsRecord->getDecl()->isUnion())
- return false;
- if (rhsRecord && rhsRecord->getDecl()->isUnion())
- return false;
+ return cast<CXXRecordDecl>(rhsRecord->getDecl())
+ ->isDerivedFrom(cast<CXXRecordDecl>(lhsRecord->getDecl()));
+}
- if (lhsRecord == rhsRecord)
- return true;
+static bool EvaluateBinaryTypeTrait(Sema &Self, TypeTrait BTT, const TypeSourceInfo *Lhs,
+ const TypeSourceInfo *Rhs, SourceLocation KeyLoc) {
+ QualType LhsT = Lhs->getType();
+ QualType RhsT = Rhs->getType();
- // C++0x [meta.rel]p2:
- // If Base and Derived are class types and are different types
- // (ignoring possible cv-qualifiers) then Derived shall be a
- // complete type.
- if (Self.RequireCompleteType(
- Rhs->getTypeLoc().getBeginLoc(), RhsT,
- diag::err_incomplete_type_used_in_type_trait_expr))
- return false;
+ assert(!LhsT->isDependentType() && !RhsT->isDependentType() &&
+ "Cannot evaluate traits of dependent types");
+
+ switch(BTT) {
+ case BTT_IsBaseOf:
+ return Self.BuiltinIsBaseOf(Rhs->getTypeLoc().getBeginLoc(), LhsT, RhsT);
- return cast<CXXRecordDecl>(rhsRecord->getDecl())
- ->isDerivedFrom(cast<CXXRecordDecl>(lhsRecord->getDecl()));
- }
case BTT_IsVirtualBaseOf: {
const RecordType *BaseRecord = LhsT->getAs<RecordType>();
const RecordType *DerivedRecord = RhsT->getAs<RecordType>();
diff --git a/clang/test/CodeGenCXX/builtin-invoke.cpp b/clang/test/CodeGenCXX/builtin-invoke.cpp
new file mode 100644
index 0000000000000..af66dfd4dae30
--- /dev/null
+++ b/clang/test/CodeGenCXX/builtin-invoke.cpp
@@ -0,0 +1,61 @@
+// RUN: %clang_cc1 -triple=x86_64-linux-gnu -emit-llvm -o - %s | FileCheck %s
+
+extern "C" void* memcpy(void*, const void*, decltype(sizeof(int)));
+void func();
+
+namespace std {
+ template <class T>
+ class reference_wrapper {
+ T* ptr;
+
+ public:
+ T& get() { return *ptr; }
+ };
+} // namespace std
+
+struct Callable {
+ void operator()() {}
+
+ void func();
+};
+
+extern "C" void call1() {
+ __builtin_invoke(func);
+ __builtin_invoke(Callable{});
+ __builtin_invoke(memcpy, nullptr, nullptr, 0);
+
+ // CHECK: define dso_local void @call1
+ // CHECK-NEXT: entry:
+ // CHECK-NEXT: %ref.tmp = alloca %struct.Callable, align 1
+ // CHECK-NEXT: call void @_Z4funcv()
+ // CHECK-NEXT: call void @_ZN8CallableclEv(ptr noundef nonnull align 1 dereferenceable(1) %ref.tmp)
+ // CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 1 null, ptr align 1 null, i64 0, i1 false)
+ // CHECK-NEXT: ret void
+}
+
+extern "C" void call_memptr(std::reference_wrapper<Callable> wrapper) {
+ __builtin_invoke(&Callable::func, wrapper);
+
+ // CHECK: define dso_local void @call_memptr
+ // CHECK-NEXT: entry:
+ // CHECK-NEXT: %wrapper = alloca %"class.std::reference_wrapper", align 8
+ // CHECK-NEXT: %coerce.dive = getelementptr inbounds nuw %"class.std::reference_wrapper", ptr %wrapper, i32 0, i32 0
+ // CHECK-NEXT: store ptr %wrapper.coerce, ptr %coerce.dive, align 8
+ // CHECK-NEXT: %call = call noundef nonnull align 1 dereferenceable(1) ptr @_ZNSt17reference_wrapperI8CallableE3getEv(ptr noundef nonnull align 8 dereferenceable(8) %wrapper)
+ // CHECK-NEXT: %0 = getelementptr inbounds i8, ptr %call, i64 0
+ // CHECK-NEXT: br i1 false, label %memptr.virtual, label %memptr.nonvirtual
+ // CHECK-EMPTY:
+ // CHECK-NEXT: memptr.virtual:
+ // CHECK-NEXT: %vtable = load ptr, ptr %0, align 8
+ // CHECK-NEXT: %1 = getelementptr i8, ptr %vtable, i64 sub (i64 ptrtoint (ptr @_ZN8Callable4funcEv to i64), i64 1), !nosanitize !2
+ // CHECK-NEXT: %memptr.virtualfn = load ptr, ptr %1, align 8, !nosanitize !2
+ // CHECK-NEXT: br label %memptr.end
+ // CHECK-EMPTY:
+ // CHECK-NEXT: memptr.nonvirtual:
+ // CHECK-NEXT: br label %memptr.end
+ // CHECK-EMPTY:
+ // CHECK-NEXT: memptr.end:
+ // CHECK-NEXT: %2 = phi ptr [ %memptr.virtualfn, %memptr.virtual ], [ @_ZN8Callable4funcEv, %memptr.nonvirtual ]
+ // CHECK-NEXT: call void %2(ptr noundef nonnull align 1 dereferenceable(1) %0)
+ // CHECK-NEXT: ret void
+}
diff --git a/clang/test/SemaCXX/builtin-invoke.cpp b/clang/test/SemaCXX/builtin-invoke.cpp
new file mode 100644
index 0000000000000..5b156b5ff75c4
--- /dev/null
+++ b/clang/test/SemaCXX/builtin-invoke.cpp
@@ -0,0 +1,133 @@
+// RUN: %clang_cc1 -verify -fsyntax-only %s
+
+void func() { // expected-note {{'func' declared here}}
+ __builtin_invoke(); // expected-error {{too few arguments to function call, expected at least 1, have 0}}
+}
+
+void nfunc() noexcept {}
+
+struct S {};
+void argfunc(int, S) {} // expected-note {{'argfunc' declared here}}
+
+struct Callable {
+ void operator()() {}
+
+ void func() {}
+
+ int var;
+};
+
+void* malloc(decltype(sizeof(int)));
+
+template <class T>
+struct pointer_wrapper {
+ T* v;
+
+ T& operator*() {
+ return *v;
+ }
+};
+
+namespace std {
+ template <class T>
+ class reference_wrapper {
+ T* ptr;
+
+ public:
+ reference_wrapper(T& ref) : ptr(&ref) {}
+
+ T& get() { return *ptr; }
+ };
+
+ template <class T>
+ reference_wrapper<T> ref(T& v) {
+ return reference_wrapper<T>(v);
+ }
+} // namespace std
+
+struct InvalidSpecialization1 {
+ void func() {}
+
+ int var;
+};
+
+template <>
+class std::reference_wrapper<InvalidSpecialization1> {
+public:
+ reference_wrapper(InvalidSpecialization1&) {}
+};
+
+struct InvalidSpecialization2 {
+ void func() {}
+
+ int var;
+};
+
+template <>
+class std::reference_wrapper<InvalidSpecialization2> {
+public:
+ reference_wrapper(InvalidSpecialization2&) {}
+
+private:
+ InvalidSpecialization2& get(); // expected-note 2 {{declared private here}}
+};
+
+void call() {
+ __builtin_invoke(func);
+ __builtin_invoke(nfunc);
+ static_assert(!noexcept(__builtin_invoke(func)));
+ static_assert(noexcept(__builtin_invoke(nfunc)));
+ __builtin_invoke(func, 1); // expected-error {{too many arguments to function call, expected 0, have 1}}
+ __builtin_invoke(argfunc, 1); // expected-error {{too few arguments to function call, expected 2, have 1}}
+ __builtin_invoke(Callable{});
+ __builtin_invoke(malloc, 0);
+ __builtin_invoke(__builtin_malloc, 0); // expected-error {{builtin functions must be directly called}}
+
+ // Member functiom pointer
+ __builtin_invoke(&Callable::func); // expected-error {{too few arguments to function call, expected at least 2, have 1}}
+ __builtin_invoke(&Callable::func, 1); // expected-error {{indirection requires pointer operand ('int' invalid)}}
+ __builtin_invoke(&Callable::func, Callable{});
+ __builtin_invoke(&Callable::func, Callable{}, 1); // expected-error {{too many arguments to function call, expected 0, have 1}}
+
+ Callable c;
+ __builtin_invoke(&Callable::func, &c);
+ __builtin_invoke(&Callable::func, std::ref(c));
+ __builtin_invoke(&Callable::func, &c);
+ __builtin_invoke(&Callable::func, &c, 2); // expected-error {{too many arguments to function call, expected 0, have 1}}
+ __builtin_invoke(&Callable::func, pointer_wrapper<Callable>{&c});
+ __builtin_invoke(&Callable::func, pointer_wrapper<Callable>{&c}, 2); // expected-error {{too many arguments to function call, expected 0, have 1}}
+
+ InvalidSpecialization1 is1;
+ InvalidSpecialization2 is2;
+ __builtin_invoke(&InvalidSpecialization1::func, std::ref(is1)); // expected-error {{no member named 'get' in 'std::reference_wrapper<InvalidSpecialization1>'}}
+ __builtin_invoke(&InvalidSpecialization2::func, std::ref(is2)); // expected-error {{'get' is a private member of 'std::reference_wrapper<InvalidSpecialization2>'}}
+
+ // Member data pointer
+ __builtin_invoke(&Callable::var); // expected-error {{too few arguments to function call, expected at least 2, have 1}}
+ __builtin_invoke(&Callable::var, 1); // expected-error {{indirection requires pointer operand ('int' invalid)}}
+ (void)__builtin_invoke(&Callable::var, Callable{});
+ __builtin_invoke(&Callable::var, Callable{}, 1); // expected-error {{too many arguments to function call, expected 2, have 3}}
+
+ (void)__builtin_invoke(&Callable::var, &c);
+ (void)__builtin_invoke(&Callable::var, std::ref(c));
+ (void)__builtin_invoke(&Callable::var, &c);
+ __builtin_invoke(&Callable::var, &c, 2); // expected-error {{too many arguments to function call, expected 2,...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/116709
More information about the cfe-commits
mailing list