[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.
This commit is contained in:
Nikolas Klauser
2025-06-29 17:52:50 +02:00
committed by GitHub
parent 43ab5bb921
commit 713839729c
10 changed files with 624 additions and 82 deletions

View File

@@ -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``
-----------------------------------

View File

@@ -327,6 +327,7 @@ Non-comprehensive list of changes in this release
different 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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@@ -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];

View File

@@ -15192,11 +15192,18 @@ public:
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

View File

@@ -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();

View File

@@ -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 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;
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.
case BTT_IsBaseOf:
return Self.BuiltinIsBaseOf(Rhs->getTypeLoc().getBeginLoc(), LhsT, RhsT);
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;
if (lhsRecord == rhsRecord)
return true;
// 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;
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>();

View File

@@ -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
}

View File

@@ -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;
}());

View File

@@ -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

View File

@@ -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