[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:
@@ -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``
|
||||
-----------------------------------
|
||||
|
||||
@@ -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
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>();
|
||||
|
||||
61
clang/test/CodeGenCXX/builtin-invoke.cpp
Normal file
61
clang/test/CodeGenCXX/builtin-invoke.cpp
Normal 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
|
||||
}
|
||||
240
clang/test/SemaCXX/builtin-invoke.cpp
Normal file
240
clang/test/SemaCXX/builtin-invoke.cpp
Normal 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;
|
||||
}());
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user