[libcxx-commits] [libcxx] 7138397 - [Clang] Add __builtin_invoke and use it in libc++ (#116709)
via libcxx-commits
libcxx-commits at lists.llvm.org
Sun Jun 29 08:52:53 PDT 2025
Author: Nikolas Klauser
Date: 2025-06-29T17:52:50+02:00
New Revision: 713839729c97d1ac9492c516d0bf5e1add27fbd3
URL: https://github.com/llvm/llvm-project/commit/713839729c97d1ac9492c516d0bf5e1add27fbd3
DIFF: https://github.com/llvm/llvm-project/commit/713839729c97d1ac9492c516d0bf5e1add27fbd3.diff
LOG: [Clang] Add __builtin_invoke and use it in libc++ (#116709)
`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.
Added:
clang/test/CodeGenCXX/builtin-invoke.cpp
clang/test/SemaCXX/builtin-invoke.cpp
Modified:
clang/docs/LanguageExtensions.rst
clang/docs/ReleaseNotes.rst
clang/include/clang/Basic/Builtins.td
clang/include/clang/Sema/Sema.h
clang/lib/Sema/SemaChecking.cpp
clang/lib/Sema/SemaTypeTraits.cpp
libcxx/include/__type_traits/invoke.h
libcxx/include/__type_traits/is_core_convertible.h
Removed:
################################################################################
diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index 0ba301f438d04..a42a546555716 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -3798,6 +3798,17 @@ Trivially relocates ``count`` objects of relocatable, complete type ``T``
from ``src`` to ``dest`` and returns ``dest``.
This builtin is used to implement ``std::trivially_relocate``.
+``__builtin_invoke``
+--------------------
+
+**Syntax**:
+
+.. code-block:: c++
+
+ template <class Callee, class... Args>
+ decltype(auto) __builtin_invoke(Callee&& callee, Args&&... args);
+
+``__builtin_invoke`` is equivalent to ``std::invoke``.
``__builtin_preserve_access_index``
-----------------------------------
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index d9847fadc21e5..bee71cf296ac3 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -327,6 +327,7 @@ Non-comprehensive list of changes in this release
diff erent than before.
- Fixed a crash when a VLA with an invalid size expression was used within a
``sizeof`` or ``typeof`` expression. (#GH138444)
+- ``__builtin_invoke`` has been added to improve the compile time of ``std::invoke``.
- Deprecation warning is emitted for the deprecated ``__reference_binds_to_temporary`` intrinsic.
``__reference_constructs_from_temporary`` should be used instead. (#GH44056)
- Added `__builtin_get_vtable_pointer` to directly load the primary vtable pointer from a
@@ -656,7 +657,7 @@ Improvements to Clang's diagnostics
false positives in exception-heavy code, though only simple patterns
are currently recognized.
-
+
Improvements to Clang's time-trace
----------------------------------
@@ -734,7 +735,7 @@ Bug Fixes in This Version
- Fixed incorrect token location when emitting diagnostics for tokens expanded from macros. (#GH143216)
- Fixed an infinite recursion when checking constexpr destructors. (#GH141789)
- Fixed a crash when a malformed using declaration appears in a ``constexpr`` function. (#GH144264)
-- Fixed a bug when use unicode character name in macro concatenation. (#GH145240)
+- Fixed a bug when use unicode character name in macro concatenation. (#GH145240)
Bug Fixes to Compiler Builtins
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td
index 24810292d1d55..5ebb82180521d 100644
--- a/clang/include/clang/Basic/Builtins.td
+++ b/clang/include/clang/Basic/Builtins.td
@@ -4314,6 +4314,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 9afe3c7710476..3fe26f950ad51 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -15192,11 +15192,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/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 926adebf7de0f..51b22a5e122e4 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -2261,6 +2261,99 @@ static bool BuiltinCountZeroBitsGeneric(Sema &S, CallExpr *TheCall) {
return false;
}
+static ExprResult BuiltinInvoke(Sema &S, CallExpr *TheCall) {
+ SourceLocation Loc = TheCall->getBeginLoc();
+ MutableArrayRef Args(TheCall->getArgs(), TheCall->getNumArgs());
+ assert(llvm::none_of(Args, [](Expr *Arg) { return Arg->isTypeDependent(); }));
+
+ if (Args.size() == 0) {
+ S.Diag(TheCall->getBeginLoc(),
+ diag::err_typecheck_call_too_few_args_at_least)
+ << /*callee_type=*/0 << /*min_arg_count=*/1 << /*actual_arg_count=*/0
+ << /*is_non_object=*/0 << TheCall->getSourceRange();
+ return ExprError();
+ }
+
+ QualType FuncT = Args[0]->getType();
+
+ if (const auto *MPT = FuncT->getAs<MemberPointerType>()) {
+ if (Args.size() < 2) {
+ S.Diag(TheCall->getBeginLoc(),
+ diag::err_typecheck_call_too_few_args_at_least)
+ << /*callee_type=*/0 << /*min_arg_count=*/2 << /*actual_arg_count=*/1
+ << /*is_non_object=*/0 << TheCall->getSourceRange();
+ return ExprError();
+ }
+
+ const Type *MemPtrClass = MPT->getQualifier()->getAsType();
+ QualType ObjectT = Args[1]->getType();
+
+ if (MPT->isMemberDataPointer() && S.checkArgCount(TheCall, 2))
+ 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 (S.Context.hasSameType(QualType(MemPtrClass, 0),
+ S.BuiltinRemoveCVRef(ObjectT, Loc)) ||
+ S.BuiltinIsBaseOf(Args[1]->getBeginLoc(), QualType(MemPtrClass, 0),
+ S.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 (const auto *RD = ObjectT->getAsCXXRecordDecl()) {
+ if (RD->isInStdNamespace() &&
+ RD->getDeclName().getAsString() == "reference_wrapper") {
+ CXXScopeSpec SS;
+ IdentifierInfo *GetName = &S.Context.Idents.get("get");
+ UnqualifiedId GetID;
+ GetID.setIdentifier(GetName, Loc);
+
+ ExprResult MemExpr = S.ActOnMemberAccessExpr(
+ S.getCurScope(), Args[1], Loc, tok::period, SS,
+ /*TemplateKWLoc=*/SourceLocation(), GetID, nullptr);
+
+ if (MemExpr.isInvalid())
+ return ExprError();
+
+ return S.ActOnCallExpr(S.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 S.ActOnUnaryOp(S.getCurScope(), Loc, tok::star, Args[1]);
+ }();
+
+ if (ObjectArg.isInvalid())
+ return ExprError();
+
+ ExprResult BinOp = S.ActOnBinOp(S.getCurScope(), TheCall->getBeginLoc(),
+ tok::periodstar, ObjectArg.get(), Args[0]);
+ if (BinOp.isInvalid())
+ return ExprError();
+
+ if (MPT->isMemberDataPointer())
+ return BinOp;
+
+ auto *MemCall = new (S.Context)
+ ParenExpr(SourceLocation(), SourceLocation(), BinOp.get());
+
+ return S.ActOnCallExpr(S.getCurScope(), MemCall, TheCall->getBeginLoc(),
+ Args.drop_front(2), TheCall->getRParenLoc());
+ }
+ return S.ActOnCallExpr(S.getCurScope(), Args.front(), TheCall->getBeginLoc(),
+ Args.drop_front(), TheCall->getRParenLoc());
+}
+
ExprResult
Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
CallExpr *TheCall) {
@@ -2420,6 +2513,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(*this, TheCall);
case Builtin::BI__builtin_prefetch:
if (BuiltinPrefetch(TheCall))
return ExprError();
diff --git a/clang/lib/Sema/SemaTypeTraits.cpp b/clang/lib/Sema/SemaTypeTraits.cpp
index cb3d9b77ee4dd..c2f0600295e9e 100644
--- a/clang/lib/Sema/SemaTypeTraits.cpp
+++ b/clang/lib/Sema/SemaTypeTraits.cpp
@@ -1579,6 +1579,58 @@ ExprResult Sema::ActOnTypeTrait(TypeTrait Kind, SourceLocation KWLoc,
return BuildTypeTrait(Kind, KWLoc, ConvertedArgs, RParenLoc);
}
+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;
+
+ ObjCInterfaceDecl *BaseInterface = LHSObjTy->getInterface();
+ ObjCInterfaceDecl *DerivedInterface = RHSObjTy->getInterface();
+ if (!BaseInterface || !DerivedInterface)
+ return false;
+
+ if (RequireCompleteType(RhsTLoc, RhsT,
+ diag::err_incomplete_type_used_in_type_trait_expr))
+ return false;
+
+ return BaseInterface->isSuperClassOf(DerivedInterface);
+ }
+
+ assert(Context.hasSameUnqualifiedType(LhsT, RhsT) ==
+ (lhsRecord == rhsRecord));
+
+ // 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;
+
+ // C++0x [meta.rel]p2:
+ // If Base and Derived are class types and are
diff erent 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;
+
+ return cast<CXXRecordDecl>(rhsRecord->getDecl())
+ ->isDerivedFrom(cast<CXXRecordDecl>(lhsRecord->getDecl()));
+}
+
static bool EvaluateBinaryTypeTrait(Sema &Self, TypeTrait BTT,
const TypeSourceInfo *Lhs,
const TypeSourceInfo *Rhs,
@@ -1590,58 +1642,9 @@ static bool EvaluateBinaryTypeTrait(Sema &Self, TypeTrait BTT,
"Cannot evaluate traits of dependent types");
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;
-
- ObjCInterfaceDecl *BaseInterface = LHSObjTy->getInterface();
- ObjCInterfaceDecl *DerivedInterface = RHSObjTy->getInterface();
- if (!BaseInterface || !DerivedInterface)
- return false;
-
- if (Self.RequireCompleteType(
- Rhs->getTypeLoc().getBeginLoc(), RhsT,
- diag::err_incomplete_type_used_in_type_trait_expr))
- return false;
-
- return BaseInterface->isSuperClassOf(DerivedInterface);
- }
-
- assert(Self.Context.hasSameUnqualifiedType(LhsT, RhsT) ==
- (lhsRecord == rhsRecord));
-
- // 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;
+ case BTT_IsBaseOf:
+ return Self.BuiltinIsBaseOf(Rhs->getTypeLoc().getBeginLoc(), LhsT, RhsT);
- if (lhsRecord == rhsRecord)
- return true;
-
- // C++0x [meta.rel]p2:
- // If Base and Derived are class types and are
diff erent 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;
-
- 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..d7694d619c692
--- /dev/null
+++ b/clang/test/SemaCXX/builtin-invoke.cpp
@@ -0,0 +1,240 @@
+// RUN: %clang_cc1 -verify -fsyntax-only %s -std=c++23
+
+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:
+ constexpr reference_wrapper(T& ref) : ptr(&ref) {}
+
+ constexpr T& get() { return *ptr; }
+ };
+
+ template <class T>
+ constexpr 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}}
+};
+
+struct ExplicitObjectParam {
+ void func(this const ExplicitObjectParam& self) {}
+};
+
+struct Incomplete; // expected-note 2 {{forward declaration}}
+struct Incomplete2;
+
+void incomplete_by_val_test(Incomplete);
+
+void incomplete_test(Incomplete& incomplete) {
+ __builtin_invoke((int (Incomplete2::*)){}, incomplete); // expected-error {{incomplete type 'Incomplete' used in type trait expression}} \
+ expected-error {{indirection requires pointer operand ('Incomplete' invalid)}}
+ __builtin_invoke(incomplete_test, incomplete);
+ __builtin_invoke(incomplete_by_val_test, incomplete); // expected-error {{argument type 'Incomplete' is incomplete}}
+}
+
+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}}
+
+ // Variadic function
+ void variadic_func(int, ...); // expected-note {{declared here}}
+
+ __builtin_invoke(variadic_func); // expected-error {{too few arguments to function call, expected at least 1, have 0}}
+ __builtin_invoke(variadic_func, 1);
+ __builtin_invoke(variadic_func, 1, 2, 3);
+
+ // static member function
+ struct StaticMember {
+ static void func(int);
+ };
+
+ __builtin_invoke(StaticMember::func, 1);
+ StaticMember sm;
+ __builtin_invoke(sm.func, 1);
+
+ // lambda
+ __builtin_invoke([] {});
+ __builtin_invoke([](int) {}, 1);
+
+ // Member function 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}}
+ __builtin_invoke(&ExplicitObjectParam::func, ExplicitObjectParam{});
+
+ 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, have 3}}
+ (void)__builtin_invoke(&Callable::var, pointer_wrapper<Callable>{&c});
+ __builtin_invoke(&Callable::var, pointer_wrapper<Callable>{&c}, 2); // expected-error {{too many arguments to function call, expected 2, have 3}}
+
+ __builtin_invoke(&InvalidSpecialization1::var, std::ref(is1)); // expected-error {{no member named 'get' in 'std::reference_wrapper<InvalidSpecialization1>'}}
+ (void)__builtin_invoke(&InvalidSpecialization2::var, std::ref(is2)); // expected-error {{'get' is a private member of 'std::reference_wrapper<InvalidSpecialization2>'}}
+}
+
+[[nodiscard]] int diagnose_discard();
+int no_diagnose_discard();
+
+namespace std {
+ template <class... Args>
+ auto invoke(Args&&... args) -> decltype(__builtin_invoke(args...));
+} // namespace std
+
+template <class... Args>
+concept invocable = requires(Args... args) { __builtin_invoke(args...); };
+
+static_assert(!invocable<std::reference_wrapper<InvalidSpecialization1>>);
+static_assert(!invocable<std::reference_wrapper<InvalidSpecialization2>>);
+
+void test3() {
+ __builtin_invoke(diagnose_discard); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+ __builtin_invoke(no_diagnose_discard);
+}
+
+template <class T>
+auto test(T v) {
+ return __builtin_invoke(v);
+}
+
+auto call2() {
+ test(call);
+}
+
+template <class ClassT, class FuncT>
+void func(ClassT& c, FuncT&& func) {
+ __builtin_invoke(func, c, 1, 2, 3); // expected-error {{too many arguments to function call, expected 0, have 3}}
+}
+
+struct DependentTest {
+ void func(int, int, int);
+ void bad_func();
+};
+
+void call3() {
+ DependentTest d;
+ func(d, &DependentTest::func);
+ func(d, &DependentTest::bad_func); // expected-note {{requested here}}
+}
+
+constexpr int constexpr_func() {
+ return 42;
+}
+
+struct ConstexprTestStruct {
+ int i;
+ constexpr int func() {
+ return 55;
+ }
+};
+
+// Make sure that constant evaluation works
+static_assert([]() {
+
+ ConstexprTestStruct s;
+ if (__builtin_invoke(&ConstexprTestStruct::func, s) != 55) // [func.requires]/p1.1
+ return false;
+ if (__builtin_invoke(&ConstexprTestStruct::func, std::ref(s)) != 55) // [func.requires]/p1.2
+ return false;
+ if (__builtin_invoke(&ConstexprTestStruct::func, &s) != 55) // [func.requires]/p1.3
+ return false;
+
+ s.i = 22;
+ if (__builtin_invoke(&ConstexprTestStruct::i, s) != 22) // [func.requires]/p1.4
+ return false;
+ if (__builtin_invoke(&ConstexprTestStruct::i, std::ref(s)) != 22) // [func.requires]/p1.5
+ return false;
+ if (__builtin_invoke(&ConstexprTestStruct::i, &s) != 22) // [func.requires]/p1.6
+ return false;
+
+ // [func.requires]/p1.7
+ if (__builtin_invoke(constexpr_func) != 42)
+ return false;
+ if (__builtin_invoke([] { return 34; }) != 34)
+ return false;
+
+ return true;
+}());
diff --git a/libcxx/include/__type_traits/invoke.h b/libcxx/include/__type_traits/invoke.h
index ccf86209d2295..e58fad75e9215 100644
--- a/libcxx/include/__type_traits/invoke.h
+++ b/libcxx/include/__type_traits/invoke.h
@@ -22,6 +22,7 @@
#include <__type_traits/is_same.h>
#include <__type_traits/is_void.h>
#include <__type_traits/nat.h>
+#include <__type_traits/void_t.h>
#include <__utility/declval.h>
#include <__utility/forward.h>
@@ -61,6 +62,112 @@
_LIBCPP_BEGIN_NAMESPACE_STD
+#if __has_builtin(__builtin_invoke)
+
+template <class... _Args>
+using __invoke_result_t = decltype(__builtin_invoke(std::declval<_Args>()...));
+
+template <class, class... _Args>
+struct __invoke_result_impl {};
+
+template <class... _Args>
+struct __invoke_result_impl<__void_t<__invoke_result_t<_Args...> >, _Args...> {
+ using type _LIBCPP_NODEBUG = __invoke_result_t<_Args...>;
+};
+
+template <class... _Args>
+using __invoke_result = __invoke_result_impl<void, _Args...>;
+
+template <class... _Args>
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR __invoke_result_t<_Args...> __invoke(_Args&&... __args)
+ _NOEXCEPT_(noexcept(__builtin_invoke(std::forward<_Args>(__args)...))) {
+ return __builtin_invoke(std::forward<_Args>(__args)...);
+}
+
+template <class _Void, class... _Args>
+inline const bool __is_invocable_impl = false;
+
+template <class... _Args>
+inline const bool __is_invocable_impl<__void_t<__invoke_result_t<_Args...> >, _Args...> = true;
+
+template <class... _Args>
+inline const bool __is_invocable_v = __is_invocable_impl<void, _Args...>;
+
+template <class... _Args>
+struct __is_invocable : integral_constant<bool, __is_invocable_v<_Args...> > {};
+
+template <class _Ret, bool, class... _Args>
+inline const bool __is_invocable_r_impl = false;
+
+template <class _Ret, class... _Args>
+inline const bool __is_invocable_r_impl<_Ret, true, _Args...> =
+ __is_core_convertible<__invoke_result_t<_Args...>, _Ret>::value || is_void<_Ret>::value;
+
+template <class _Ret, class... _Args>
+inline const bool __is_invocable_r_v = __is_invocable_r_impl<_Ret, __is_invocable_v<_Args...>, _Args...>;
+
+template <bool __is_invocable, class... _Args>
+inline const bool __is_nothrow_invocable_impl = false;
+
+template <class... _Args>
+inline const bool __is_nothrow_invocable_impl<true, _Args...> = noexcept(__builtin_invoke(std::declval<_Args>()...));
+
+template <class... _Args>
+inline const bool __is_nothrow_invocable_v = __is_nothrow_invocable_impl<__is_invocable_v<_Args...>, _Args...>;
+
+template <bool __is_invocable, class _Ret, class... _Args>
+inline const bool __is_nothrow_invocable_r_impl = false;
+
+template <class _Ret, class... _Args>
+inline const bool __is_nothrow_invocable_r_impl<true, _Ret, _Args...> =
+ __is_nothrow_core_convertible_v<__invoke_result_t<_Args...>, _Ret> || is_void<_Ret>::value;
+
+template <class _Ret, class... _Args>
+inline const bool __is_nothrow_invocable_r_v =
+ __is_nothrow_invocable_r_impl<__is_nothrow_invocable_v<_Args...>, _Ret, _Args...>;
+
+# if _LIBCPP_STD_VER >= 17
+
+// is_invocable
+
+template <class _Fn, class... _Args>
+struct _LIBCPP_NO_SPECIALIZATIONS is_invocable : bool_constant<__is_invocable_v<_Fn, _Args...> > {};
+
+template <class _Ret, class _Fn, class... _Args>
+struct _LIBCPP_NO_SPECIALIZATIONS is_invocable_r : bool_constant<__is_invocable_r_v<_Ret, _Fn, _Args...>> {};
+
+template <class _Fn, class... _Args>
+_LIBCPP_NO_SPECIALIZATIONS inline constexpr bool is_invocable_v = __is_invocable_v<_Fn, _Args...>;
+
+template <class _Ret, class _Fn, class... _Args>
+_LIBCPP_NO_SPECIALIZATIONS inline constexpr bool is_invocable_r_v = is_invocable_r<_Ret, _Fn, _Args...>::value;
+
+// is_nothrow_invocable
+
+template <class _Fn, class... _Args>
+struct _LIBCPP_NO_SPECIALIZATIONS is_nothrow_invocable : bool_constant<__is_nothrow_invocable_v<_Fn, _Args...> > {};
+
+template <class _Ret, class _Fn, class... _Args>
+struct _LIBCPP_NO_SPECIALIZATIONS is_nothrow_invocable_r
+ : integral_constant<bool, __is_nothrow_invocable_r_v<_Ret, _Fn, _Args...>> {};
+
+template <class _Fn, class... _Args>
+_LIBCPP_NO_SPECIALIZATIONS inline constexpr bool is_nothrow_invocable_v = __is_nothrow_invocable_v<_Fn, _Args...>;
+
+template <class _Ret, class _Fn, class... _Args>
+_LIBCPP_NO_SPECIALIZATIONS inline constexpr bool is_nothrow_invocable_r_v =
+ __is_nothrow_invocable_r_v<_Ret, _Fn, _Args...>;
+
+template <class _Fn, class... _Args>
+struct _LIBCPP_NO_SPECIALIZATIONS invoke_result : __invoke_result<_Fn, _Args...> {};
+
+template <class _Fn, class... _Args>
+using invoke_result_t = __invoke_result_t<_Fn, _Args...>;
+
+# endif // _LIBCPP_STD_VER >= 17
+
+#else // __has_builtin(__builtin_invoke)
+
template <class _DecayedFp>
struct __member_pointer_class_type {};
@@ -211,21 +318,21 @@ struct __nothrow_invokable_r_imp<true, false, _Ret, _Fp, _Args...> {
template <class _Tp>
static void __test_noexcept(_Tp) _NOEXCEPT;
-#ifdef _LIBCPP_CXX03_LANG
+# ifdef _LIBCPP_CXX03_LANG
static const bool value = false;
-#else
+# else
static const bool value =
noexcept(_ThisT::__test_noexcept<_Ret>(std::__invoke(std::declval<_Fp>(), std::declval<_Args>()...)));
-#endif
+# endif
};
template <class _Ret, class _Fp, class... _Args>
struct __nothrow_invokable_r_imp<true, true, _Ret, _Fp, _Args...> {
-#ifdef _LIBCPP_CXX03_LANG
+# ifdef _LIBCPP_CXX03_LANG
static const bool value = false;
-#else
+# else
static const bool value = noexcept(std::__invoke(std::declval<_Fp>(), std::declval<_Args>()...));
-#endif
+# endif
};
template <class _Ret, class _Fp, class... _Args>
@@ -236,22 +343,6 @@ template <class _Fp, class... _Args>
using __nothrow_invokable _LIBCPP_NODEBUG =
__nothrow_invokable_r_imp<__is_invocable<_Fp, _Args...>::value, true, void, _Fp, _Args...>;
-template <class _Ret, bool = is_void<_Ret>::value>
-struct __invoke_void_return_wrapper {
- template <class... _Args>
- _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 static _Ret __call(_Args&&... __args) {
- return std::__invoke(std::forward<_Args>(__args)...);
- }
-};
-
-template <class _Ret>
-struct __invoke_void_return_wrapper<_Ret, true> {
- template <class... _Args>
- _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 static void __call(_Args&&... __args) {
- std::__invoke(std::forward<_Args>(__args)...);
- }
-};
-
template <class _Func, class... _Args>
inline const bool __is_invocable_v = __is_invocable<_Func, _Args...>::value;
@@ -268,12 +359,7 @@ struct __invoke_result
template <class _Func, class... _Args>
using __invoke_result_t _LIBCPP_NODEBUG = typename __invoke_result<_Func, _Args...>::type;
-template <class _Ret, class... _Args>
-_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _Ret __invoke_r(_Args&&... __args) {
- return __invoke_void_return_wrapper<_Ret>::__call(std::forward<_Args>(__args)...);
-}
-
-#if _LIBCPP_STD_VER >= 17
+# if _LIBCPP_STD_VER >= 17
// is_invocable
@@ -311,7 +397,30 @@ struct _LIBCPP_NO_SPECIALIZATIONS invoke_result : __invoke_result<_Fn, _Args...>
template <class _Fn, class... _Args>
using invoke_result_t = typename invoke_result<_Fn, _Args...>::type;
-#endif // _LIBCPP_STD_VER >= 17
+# endif // _LIBCPP_STD_VER >= 17
+
+#endif // __has_builtin(__builtin_invoke_r)
+
+template <class _Ret, bool = is_void<_Ret>::value>
+struct __invoke_void_return_wrapper {
+ template <class... _Args>
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 static _Ret __call(_Args&&... __args) {
+ return std::__invoke(std::forward<_Args>(__args)...);
+ }
+};
+
+template <class _Ret>
+struct __invoke_void_return_wrapper<_Ret, true> {
+ template <class... _Args>
+ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 static void __call(_Args&&... __args) {
+ std::__invoke(std::forward<_Args>(__args)...);
+ }
+};
+
+template <class _Ret, class... _Args>
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _Ret __invoke_r(_Args&&... __args) {
+ return __invoke_void_return_wrapper<_Ret>::__call(std::forward<_Args>(__args)...);
+}
_LIBCPP_END_NAMESPACE_STD
diff --git a/libcxx/include/__type_traits/is_core_convertible.h b/libcxx/include/__type_traits/is_core_convertible.h
index ca3a346c17cd7..0b8e94e101db1 100644
--- a/libcxx/include/__type_traits/is_core_convertible.h
+++ b/libcxx/include/__type_traits/is_core_convertible.h
@@ -37,6 +37,15 @@ concept __core_convertible_to = __is_core_convertible<_Tp, _Up>::value;
#endif // _LIBCPP_STD_VER >= 20
+template <class _Tp, class _Up, bool = __is_core_convertible<_Tp, _Up>::value>
+inline const bool __is_nothrow_core_convertible_v = false;
+
+#ifndef _LIBCPP_CXX03_LANG
+template <class _Tp, class _Up>
+inline const bool __is_nothrow_core_convertible_v<_Tp, _Up, true> =
+ noexcept(static_cast<void (*)(_Up) noexcept>(0)(static_cast<_Tp (*)() noexcept>(0)()));
+#endif
+
_LIBCPP_END_NAMESPACE_STD
#endif // _LIBCPP___TYPE_TRAITS_IS_CORE_CONVERTIBLE_H
More information about the libcxx-commits
mailing list