[libcxx-commits] [clang] [libcxx] [Clang] Add __is_invocable_r and __is_nothrow_invocable_r (PR #81213)

Nikolas Klauser via libcxx-commits libcxx-commits at lists.llvm.org
Thu Feb 8 17:36:21 PST 2024


https://github.com/philnik777 created https://github.com/llvm/llvm-project/pull/81213

This patch also uses the new builtins in libc++.


>From 6c74eb263dd889858f3f7be328d85fe354f71835 Mon Sep 17 00:00:00 2001
From: Nikolas Klauser <nikolasklauser at berlin.de>
Date: Mon, 11 Sep 2023 04:31:02 +0200
Subject: [PATCH] [Clang] Add __is_invocable_r and __is_nothrow_invocable_r

This patch also uses the new builtins in libc++.
---
 clang/docs/LanguageExtensions.rst            |   2 +
 clang/docs/ReleaseNotes.rst                  |   4 +
 clang/include/clang/AST/Type.h               |   3 +
 clang/include/clang/Basic/TokenKinds.def     |   2 +
 clang/lib/Sema/SemaExprCXX.cpp               | 390 +++++++++++++------
 clang/test/SemaCXX/type-traits-invocable.cpp | 288 ++++++++++++++
 libcxx/include/__type_traits/invoke.h        |  36 ++
 7 files changed, 606 insertions(+), 119 deletions(-)
 create mode 100644 clang/test/SemaCXX/type-traits-invocable.cpp

diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index e91156837290f7..bf061590e5ca0b 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -1582,6 +1582,7 @@ The following type trait primitives are supported by Clang. Those traits marked
 * ``__is_integral`` (C++, Embarcadero)
 * ``__is_interface_class`` (Microsoft):
   Returns ``false``, even for types defined with ``__interface``.
+* ``__is_invocable_r`` (Clang)
 * ``__is_literal`` (Clang):
   Synonym for ``__is_literal_type``.
 * ``__is_literal_type`` (C++, GNU, Microsoft):
@@ -1594,6 +1595,7 @@ The following type trait primitives are supported by Clang. Those traits marked
 * ``__is_nothrow_assignable`` (C++, MSVC 2013)
 * ``__is_nothrow_constructible`` (C++, MSVC 2013)
 * ``__is_nothrow_destructible`` (C++, MSVC 2013)
+* ``__is_nothrow_invocable_r`` (Clang)
 * ``__is_nullptr`` (C++, GNU, Microsoft, Embarcadero):
   Returns true for ``std::nullptr_t`` and false for everything else. The
   corresponding standard library feature is ``std::is_null_pointer``, but
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index cd8a82f281f52a..8a4ae646c008f6 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -78,6 +78,10 @@ here. Generic improvements to Clang as a whole or to its underlying
 infrastructure are described first, followed by language-specific
 sections with improvements to Clang's support for those languages.
 
+- The builtins `__is_invocable_r` and `__is_nothrow_invocable_r` have been added.
+  These are equivalent to the standard library builtins `std::is_invocable_r`
+  and `std::is_nothrow_invocable_r`.
+
 C++ Language Changes
 --------------------
 
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index d6a55f39a4bede..fe10391ac7057c 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -926,6 +926,9 @@ class QualType {
   /// Return true if this is a trivially equality comparable type.
   bool isTriviallyEqualityComparableType(const ASTContext &Context) const;
 
+  /// Returns true if this is an invocable type.
+  bool isInvocableType() const;
+
   /// Returns true if it is a class and it might be dynamic.
   bool mayBeDynamicClass() const;
 
diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def
index 23817cde7a9354..6467a52c82cb5b 100644
--- a/clang/include/clang/Basic/TokenKinds.def
+++ b/clang/include/clang/Basic/TokenKinds.def
@@ -532,6 +532,8 @@ TYPE_TRAIT_1(__is_unbounded_array, IsUnboundedArray, KEYCXX)
 TYPE_TRAIT_1(__is_nullptr, IsNullPointer, KEYCXX)
 TYPE_TRAIT_1(__is_scoped_enum, IsScopedEnum, KEYCXX)
 TYPE_TRAIT_1(__is_referenceable, IsReferenceable, KEYCXX)
+TYPE_TRAIT_N(__is_invocable_r, IsInvocableR, KEYCXX)
+TYPE_TRAIT_N(__is_nothrow_invocable_r, IsNothrowInvocableR, KEYCXX)
 TYPE_TRAIT_1(__can_pass_in_regs, CanPassInRegs, KEYCXX)
 TYPE_TRAIT_2(__reference_binds_to_temporary, ReferenceBindsToTemporary, KEYCXX)
 TYPE_TRAIT_2(__reference_constructs_from_temporary, ReferenceConstructsFromTemporary, KEYCXX)
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index 246d2313e089f3..4f4b6492e58880 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -5468,6 +5468,266 @@ static bool EvaluateUnaryTypeTrait(Sema &Self, TypeTrait UTT,
 static bool EvaluateBinaryTypeTrait(Sema &Self, TypeTrait BTT, QualType LhsT,
                                     QualType RhsT, SourceLocation KeyLoc);
 
+static bool IsBaseOf(Sema &Self, QualType LhsT, QualType RhsT,
+                     SourceLocation KeyLoc) {
+  // 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(
+            KeyLoc, 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(
+          KeyLoc, RhsT, diag::err_incomplete_type_used_in_type_trait_expr))
+    return false;
+
+  return cast<CXXRecordDecl>(rhsRecord->getDecl())
+      ->isDerivedFrom(cast<CXXRecordDecl>(lhsRecord->getDecl()));
+}
+
+static bool IsConvertible(Sema& Self, QualType LhsT, QualType RhsT, SourceLocation KeyLoc, bool CheckNothrow) {
+  // C++0x [meta.rel]p4:
+  //   Given the following function prototype:
+  //
+  //     template <class T>
+  //       typename add_rvalue_reference<T>::type create();
+  //
+  //   the predicate condition for a template specialization
+  //   is_convertible<From, To> shall be satisfied if and only if
+  //   the return expression in the following code would be
+  //   well-formed, including any implicit conversions to the return
+  //   type of the function:
+  //
+  //     To test() {
+  //       return create<From>();
+  //     }
+  //
+  //   Access checking is performed as if in a context unrelated to To and
+  //   From. Only the validity of the immediate context of the expression
+  //   of the return-statement (including conversions to the return type)
+  //   is considered.
+  //
+  // We model the initialization as a copy-initialization of a temporary
+  // of the appropriate type, which for this expression is identical to the
+  // return statement (since NRVO doesn't apply).
+
+  // Functions aren't allowed to return function or array types.
+  if (RhsT->isFunctionType() || RhsT->isArrayType())
+    return false;
+
+  // A return statement in a void function must have void type.
+  if (RhsT->isVoidType())
+    return LhsT->isVoidType();
+
+  // A function definition requires a complete, non-abstract return type.
+  if (!Self.isCompleteType(KeyLoc, RhsT) || Self.isAbstractType(KeyLoc, RhsT))
+    return false;
+
+  // Compute the result of add_rvalue_reference.
+  if (LhsT->isObjectType() || LhsT->isFunctionType())
+    LhsT = Self.Context.getRValueReferenceType(LhsT);
+
+  // Build a fake source and destination for initialization.
+  InitializedEntity To(InitializedEntity::InitializeTemporary(RhsT));
+  OpaqueValueExpr From(KeyLoc, LhsT.getNonLValueExprType(Self.Context),
+                       Expr::getValueKindForType(LhsT));
+  Expr *FromPtr = &From;
+  InitializationKind Kind(
+      InitializationKind::CreateCopy(KeyLoc, SourceLocation()));
+
+  // Perform the initialization in an unevaluated context within a SFINAE
+  // trap at translation unit scope.
+  EnterExpressionEvaluationContext Unevaluated(
+      Self, Sema::ExpressionEvaluationContext::Unevaluated);
+  Sema::SFINAETrap SFINAE(Self, /*AccessCheckingSFINAE=*/true);
+  Sema::ContextRAII TUContext(Self, Self.Context.getTranslationUnitDecl());
+  InitializationSequence Init(Self, To, Kind, FromPtr);
+  if (Init.Failed())
+    return false;
+
+  ExprResult Result = Init.Perform(Self, To, Kind, FromPtr);
+  if (Result.isInvalid() || SFINAE.hasErrorOccurred())
+    return false;
+
+  if (!CheckNothrow)
+    return true;
+  return Self.canThrow(Result.get()) == CT_Cannot;
+}
+
+static bool RequireAllCompleteTypes(Sema &S, ArrayRef<TypeSourceInfo *> Args,
+                                    SourceLocation KWLoc) {
+  for (const auto *TSI : Args) {
+    QualType ArgTy = TSI->getType();
+    if (ArgTy->isVoidType() || ArgTy->isIncompleteArrayType())
+      continue;
+
+    if (S.RequireCompleteType(
+            KWLoc, ArgTy, diag::err_incomplete_type_used_in_type_trait_expr))
+      return true;
+  }
+  return false;
+}
+
+static bool IsInvocable(Sema &S, SourceLocation KWLoc,
+                        ArrayRef<TypeSourceInfo *> Args,
+                        SourceLocation RParenLoc, bool CheckNothrow) {
+  assert(!Args.empty());
+
+  if (Args.size() == 1) {
+    S.Diag(RParenLoc, diag::err_type_trait_arity)
+        << 2 << 1 << 1 << Args.size() << 1;
+    return false;
+  }
+
+  if (RequireAllCompleteTypes(S, Args, KWLoc))
+    return false;
+
+  auto ReturnType = Args[0]->getType();
+  auto FunctorType = Args[1]->getType();
+  auto NonRefFunctorType = FunctorType.getNonReferenceType();
+
+  const auto IsConvertibleToReturnType = [&](QualType InvokeResult) {
+    return ReturnType->isVoidType() ||
+           IsConvertible(S, InvokeResult, ReturnType, RParenLoc, CheckNothrow);
+  };
+
+  const auto BuildCallHelper = [&](size_t IgnoreCount, auto CallChecker) {
+    EnterExpressionEvaluationContext Unevaluated(
+        S, Sema::ExpressionEvaluationContext::Unevaluated);
+    Sema::SFINAETrap SFINAE(S, true);
+
+    const auto ArgCount = Args.size() - IgnoreCount;
+
+    auto Deleter = [&](OpaqueValueExpr *Expr) {
+      for (size_t i = 0; i != ArgCount; ++i)
+        std::destroy_at(Expr + i);
+      std::allocator<OpaqueValueExpr>{}.deallocate(Expr, ArgCount);
+    };
+
+    // FIXME: There is most likely a much better way to achieve this
+    std::unique_ptr<OpaqueValueExpr[], decltype(Deleter) &> ArgExprs(
+        std::allocator<OpaqueValueExpr>{}.allocate(ArgCount), Deleter);
+    std::unique_ptr<Expr *[]> ArgExprPtrs =
+        std::make_unique<Expr *[]>(ArgCount);
+
+    for (auto [I, Arg] : llvm::enumerate(Args.drop_front(IgnoreCount))) {
+      ::new (ArgExprs.get() + I)
+          OpaqueValueExpr(KWLoc, Arg->getType().getNonReferenceType(),
+                          Expr::getValueKindForType(Arg->getType()));
+      ArgExprPtrs[I] = ArgExprs.get() + I;
+    }
+    return CallChecker(MutableArrayRef<Expr *>(ArgExprPtrs.get(), ArgCount));
+  };
+
+  // bullets 1-6
+  if (NonRefFunctorType->isMemberPointerType()) {
+    if (Args.size() < 3)
+      return false;
+
+    auto *MemberPointerT = NonRefFunctorType->getAs<MemberPointerType>();
+
+    auto Object = Args[2]->getType().getNonReferenceType();
+
+    // bullets 3, 6 - ignore pointers
+    if (Object->isPointerType())
+      Object = Object->getPointeeType();
+    // bullets 2, 5 - ignore reference_wrapper
+    else if (auto* RD = Object->getAsCXXRecordDecl()) {
+      if (auto *TS = dyn_cast<ClassTemplateSpecializationDecl>(RD)) {
+        if (TS->isInStdNamespace() && TS->getName() == "reference_wrapper")
+          Object = TS->getTemplateArgs().get(0).getAsType();
+      }
+    }
+    // Bullets 2 and 3 are now equivalent to 1 and
+    // bullets 2 and 5 are equivalent to 4.
+
+    if (!IsBaseOf(S, MemberPointerT->getClass()->getCanonicalTypeUnqualified(),
+                  Object, RParenLoc))
+      return false;
+
+    // bullets 4-6
+    if (NonRefFunctorType->isMemberDataPointerType()) {
+      if (!IsConvertibleToReturnType(MemberPointerT->getPointeeType()))
+        return false;
+      return Args.size() == 3;
+    }
+
+    // bullets 1-3
+    return BuildCallHelper(3, [&](auto ArgExprPtrs) {
+      OpaqueValueExpr MemberPtr(KWLoc, NonRefFunctorType,
+                                Expr::getValueKindForType(FunctorType));
+      OpaqueValueExpr Obj(KWLoc, Object, Expr::getValueKindForType(Object));
+      auto *BinOp = BinaryOperator::Create(
+          S.getASTContext(), &Obj, &MemberPtr,
+          BinaryOperator::Opcode::BO_PtrMemD, S.getASTContext().BoundMemberTy,
+          clang::VK_PRValue, OK_Ordinary, KWLoc, {});
+
+      ParenExpr Parens(KWLoc, RParenLoc, BinOp);
+
+      auto Result = S.BuildCallToMemberFunction(nullptr, &Parens, KWLoc,
+                                                ArgExprPtrs, RParenLoc);
+      if (Result.isInvalid())
+        return false;
+
+      if (CheckNothrow && S.canThrow(Result.get()) != CT_Cannot)
+        return false;
+
+      return IsConvertibleToReturnType(Result.get()->getType());
+    });
+  }
+
+  // bullet 7
+  return BuildCallHelper(2, [&](auto ArgExprPtrs) {
+    OpaqueValueExpr Obj(KWLoc, NonRefFunctorType,
+                        Expr::getValueKindForType(FunctorType));
+
+    auto Result = S.BuildCallExpr(nullptr, &Obj, KWLoc, ArgExprPtrs, RParenLoc);
+
+    if (Result.isInvalid())
+      return false;
+
+    if (CheckNothrow && S.canThrow(Result.get()) != CT_Cannot)
+      return false;
+
+    return IsConvertibleToReturnType(Result.get()->getType());
+  });
+}
+
 static bool EvaluateBooleanTypeTrait(Sema &S, TypeTrait Kind,
                                      SourceLocation KWLoc,
                                      ArrayRef<TypeSourceInfo *> Args,
@@ -5600,7 +5860,12 @@ static bool EvaluateBooleanTypeTrait(Sema &S, TypeTrait Kind,
     llvm_unreachable("unhandled type trait");
     return false;
   }
-    default: llvm_unreachable("not a TT");
+  case clang::TT_IsInvocableR:
+  case clang::TT_IsNothrowInvocableR:
+    return IsInvocable(S, KWLoc, Args, RParenLoc,
+                       Kind == TT_IsNothrowInvocableR);
+
+  default: llvm_unreachable("not a TT");
   }
 
   return false;
@@ -5719,56 +5984,9 @@ static bool EvaluateBinaryTypeTrait(Sema &Self, TypeTrait BTT, QualType LhsT,
          "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(
-              KeyLoc, 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(KeyLoc, RhsT,
-                          diag::err_incomplete_type_used_in_type_trait_expr))
-      return false;
+  case BTT_IsBaseOf:
+    return IsBaseOf(Self, LhsT, RhsT, KeyLoc);
 
-    return cast<CXXRecordDecl>(rhsRecord->getDecl())
-      ->isDerivedFrom(cast<CXXRecordDecl>(lhsRecord->getDecl()));
-  }
   case BTT_IsSame:
     return Self.Context.hasSameType(LhsT, RhsT);
   case BTT_TypeCompatible: {
@@ -5780,75 +5998,9 @@ static bool EvaluateBinaryTypeTrait(Sema &Self, TypeTrait BTT, QualType LhsT,
   }
   case BTT_IsConvertible:
   case BTT_IsConvertibleTo:
-  case BTT_IsNothrowConvertible: {
-    // C++0x [meta.rel]p4:
-    //   Given the following function prototype:
-    //
-    //     template <class T>
-    //       typename add_rvalue_reference<T>::type create();
-    //
-    //   the predicate condition for a template specialization
-    //   is_convertible<From, To> shall be satisfied if and only if
-    //   the return expression in the following code would be
-    //   well-formed, including any implicit conversions to the return
-    //   type of the function:
-    //
-    //     To test() {
-    //       return create<From>();
-    //     }
-    //
-    //   Access checking is performed as if in a context unrelated to To and
-    //   From. Only the validity of the immediate context of the expression
-    //   of the return-statement (including conversions to the return type)
-    //   is considered.
-    //
-    // We model the initialization as a copy-initialization of a temporary
-    // of the appropriate type, which for this expression is identical to the
-    // return statement (since NRVO doesn't apply).
-
-    // Functions aren't allowed to return function or array types.
-    if (RhsT->isFunctionType() || RhsT->isArrayType())
-      return false;
-
-    // A return statement in a void function must have void type.
-    if (RhsT->isVoidType())
-      return LhsT->isVoidType();
-
-    // A function definition requires a complete, non-abstract return type.
-    if (!Self.isCompleteType(KeyLoc, RhsT) || Self.isAbstractType(KeyLoc, RhsT))
-      return false;
-
-    // Compute the result of add_rvalue_reference.
-    if (LhsT->isObjectType() || LhsT->isFunctionType())
-      LhsT = Self.Context.getRValueReferenceType(LhsT);
-
-    // Build a fake source and destination for initialization.
-    InitializedEntity To(InitializedEntity::InitializeTemporary(RhsT));
-    OpaqueValueExpr From(KeyLoc, LhsT.getNonLValueExprType(Self.Context),
-                         Expr::getValueKindForType(LhsT));
-    Expr *FromPtr = &From;
-    InitializationKind Kind(InitializationKind::CreateCopy(KeyLoc,
-                                                           SourceLocation()));
-
-    // Perform the initialization in an unevaluated context within a SFINAE
-    // trap at translation unit scope.
-    EnterExpressionEvaluationContext Unevaluated(
-        Self, Sema::ExpressionEvaluationContext::Unevaluated);
-    Sema::SFINAETrap SFINAE(Self, /*AccessCheckingSFINAE=*/true);
-    Sema::ContextRAII TUContext(Self, Self.Context.getTranslationUnitDecl());
-    InitializationSequence Init(Self, To, Kind, FromPtr);
-    if (Init.Failed())
-      return false;
-
-    ExprResult Result = Init.Perform(Self, To, Kind, FromPtr);
-    if (Result.isInvalid() || SFINAE.hasErrorOccurred())
-      return false;
-
-    if (BTT != BTT_IsNothrowConvertible)
-      return true;
-
-    return Self.canThrow(Result.get()) == CT_Cannot;
-  }
+  case BTT_IsNothrowConvertible:
+    return IsConvertible(Self, LhsT, RhsT, KeyLoc,
+                         BTT == BTT_IsNothrowConvertible);
 
   case BTT_IsAssignable:
   case BTT_IsNothrowAssignable:
diff --git a/clang/test/SemaCXX/type-traits-invocable.cpp b/clang/test/SemaCXX/type-traits-invocable.cpp
new file mode 100644
index 00000000000000..2b67e1e2aa3314
--- /dev/null
+++ b/clang/test/SemaCXX/type-traits-invocable.cpp
@@ -0,0 +1,288 @@
+// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fsyntax-only -verify -std=c++23 -fcxx-exceptions %s
+
+namespace std::inline __1 {
+  template <class>
+  class reference_wrapper {};
+} // namespace std::__1
+
+struct S {};
+
+struct Derived : S {};
+
+struct T {};
+
+struct U {
+  U(int) noexcept {}
+};
+
+struct V {
+  V(int i) {
+    if (i)
+      throw int{};
+  }
+};
+
+static_assert(!__is_nothrow_convertible(int, V));
+
+struct convertible_to_int {
+  operator int();
+};
+
+struct explicitly_convertible_to_int {
+  explicit operator int();
+};
+
+struct InvocableT {
+  void operator()() {}
+  int operator()(int, int, T) noexcept { return 0; }
+  void operator()(T) && {}
+};
+
+struct StaticInvocableT {
+  static void operator()() noexcept {}
+};
+
+struct Incomplete; // expected-note 2 {{forward declaration of 'Incomplete'}}
+
+// __is_invocable_r
+static_assert(__is_invocable_r()); // expected-error {{expected a type}}
+static_assert(!__is_invocable_r(Incomplete, void)); // expected-error {{incomplete type 'Incomplete' used in type trait expression}}
+static_assert(!__is_invocable_r(void)); // expected-error {{type trait requires 2 or more arguments; have 1 argument}}
+static_assert(!__is_invocable_r(void, int));
+
+using member_function_ptr_t = void (S::*)() noexcept;
+using member_function_arg_ptr_t = void (S::*)(int);
+using member_function_return_t = Derived (S::*)();
+
+// bullet 1
+static_assert(__is_invocable_r(void, member_function_ptr_t, S));
+static_assert(__is_invocable_r(void, member_function_ptr_t&, S));
+static_assert(!__is_invocable_r(void, member_function_ptr_t&, T));
+static_assert(__is_invocable_r(void, member_function_ptr_t, S&));
+static_assert(!__is_invocable_r(void, member_function_ptr_t, T&));
+static_assert(__is_invocable_r(void, member_function_arg_ptr_t, S&, int));
+static_assert(__is_invocable_r(void, member_function_arg_ptr_t, S&, int&));
+static_assert(__is_invocable_r(void, member_function_arg_ptr_t, S&, int&));
+static_assert(__is_invocable_r(void, member_function_return_t, S&));
+static_assert(__is_invocable_r(Derived, member_function_return_t, S&));
+static_assert(__is_invocable_r(S, member_function_return_t, S&));
+static_assert(!__is_invocable_r(T, member_function_return_t, S&));
+
+// bullet 2
+static_assert(__is_invocable_r(void, member_function_ptr_t, std::reference_wrapper<S>));
+static_assert(__is_invocable_r(void, member_function_ptr_t&, std::reference_wrapper<S>));
+static_assert(!__is_invocable_r(void, member_function_ptr_t&, std::reference_wrapper<T>));
+static_assert(__is_invocable_r(void, member_function_ptr_t, std::reference_wrapper<S>&));
+static_assert(!__is_invocable_r(void, member_function_ptr_t, std::reference_wrapper<T>&));
+static_assert(__is_invocable_r(void, member_function_arg_ptr_t, std::reference_wrapper<S>&, int));
+static_assert(__is_invocable_r(void, member_function_arg_ptr_t, std::reference_wrapper<S>&, int&));
+static_assert(__is_invocable_r(void, member_function_arg_ptr_t, std::reference_wrapper<S>&, int&));
+static_assert(__is_invocable_r(void, member_function_return_t, std::reference_wrapper<S>&));
+static_assert(__is_invocable_r(Derived, member_function_return_t, std::reference_wrapper<S>&));
+static_assert(__is_invocable_r(S, member_function_return_t, std::reference_wrapper<S>&));
+static_assert(!__is_invocable_r(T, member_function_return_t, std::reference_wrapper<S>&));
+
+// bullet 3
+static_assert(__is_invocable_r(void, member_function_ptr_t, S*));
+static_assert(__is_invocable_r(void, member_function_ptr_t&, S*));
+static_assert(!__is_invocable_r(void, member_function_ptr_t&, T*));
+static_assert(__is_invocable_r(void, member_function_ptr_t, S*&));
+static_assert(!__is_invocable_r(void, member_function_ptr_t, T*&));
+static_assert(__is_invocable_r(void, member_function_arg_ptr_t, S*&, int));
+static_assert(__is_invocable_r(void, member_function_arg_ptr_t, S*&, int&));
+static_assert(__is_invocable_r(void, member_function_arg_ptr_t, S*&, int&));
+static_assert(__is_invocable_r(void, member_function_return_t, S*&));
+static_assert(__is_invocable_r(Derived, member_function_return_t, S*&));
+static_assert(__is_invocable_r(S, member_function_return_t, S*&));
+static_assert(!__is_invocable_r(T, member_function_return_t, S*&));
+
+using member_ptr_t = int S::*;
+// bullet 4
+static_assert(!__is_invocable_r(void, member_ptr_t));
+static_assert(!__is_invocable_r(void, member_ptr_t, S&, int));
+static_assert(__is_invocable_r(void, member_ptr_t, S&));
+static_assert(__is_invocable_r(void, member_ptr_t&, S&));
+static_assert(!__is_invocable_r(void, member_ptr_t, T&));
+static_assert(!__is_invocable_r(void, member_ptr_t, T&, int));
+static_assert(__is_invocable_r(int, member_ptr_t, S&));
+static_assert(__is_invocable_r(long, member_ptr_t, S&));
+static_assert(!__is_invocable_r(T, member_ptr_t, S&));
+static_assert(__is_invocable_r(void, member_ptr_t, Derived&));
+static_assert(!__is_invocable_r(void, member_ptr_t, Derived&, int));
+
+// bullet 5
+static_assert(!__is_invocable_r(void, member_ptr_t, int));
+static_assert(__is_invocable_r(void, member_ptr_t, std::reference_wrapper<S>));
+static_assert(!__is_invocable_r(void, member_ptr_t, std::reference_wrapper<S>, int));
+static_assert(__is_invocable_r(void, member_ptr_t, std::reference_wrapper<S>&));
+static_assert(__is_invocable_r(void, member_ptr_t&, std::reference_wrapper<S>));
+static_assert(!__is_invocable_r(void, member_ptr_t, std::reference_wrapper<T>));
+static_assert(__is_invocable_r(int, member_ptr_t, std::reference_wrapper<S>));
+static_assert(__is_invocable_r(long, member_ptr_t, std::reference_wrapper<S>));
+static_assert(!__is_invocable_r(T, member_ptr_t, std::reference_wrapper<S>));
+static_assert(__is_invocable_r(void, member_ptr_t, std::reference_wrapper<Derived>));
+
+// bullet 6
+static_assert(__is_invocable_r(void, member_ptr_t, S*));
+static_assert(__is_invocable_r(void, member_ptr_t&, S*));
+static_assert(!__is_invocable_r(void, member_ptr_t&, S*, int));
+static_assert(!__is_invocable_r(void, member_ptr_t, T*));
+static_assert(__is_invocable_r(int, member_ptr_t, S*));
+static_assert(__is_invocable_r(long, member_ptr_t, S*));
+static_assert(!__is_invocable_r(T, member_ptr_t, S*));
+static_assert(__is_invocable_r(void, member_ptr_t, Derived*));
+
+// Bullet 7
+using func_t = void(*)() noexcept;
+using func_arg_t = void(*)(int);
+using func_arg_ref_t = void(*)(int&) noexcept;
+using func_return_t = int(*)();
+static_assert(__is_invocable_r(void, func_t));
+static_assert(__is_invocable_r(void, func_t&));
+static_assert(!__is_invocable_r(void, func_arg_t));
+static_assert(__is_invocable_r(void, func_arg_t, int));
+static_assert(__is_invocable_r(void, func_arg_t, int&));
+static_assert(__is_invocable_r(void, func_arg_t, const int&));
+static_assert(__is_invocable_r(void, func_arg_t, short));
+static_assert(__is_invocable_r(void, func_arg_t, char));
+static_assert(__is_invocable_r(void, func_arg_t, long));
+static_assert(__is_invocable_r(void, func_arg_t, double));
+static_assert(__is_invocable_r(void, func_arg_t, convertible_to_int));
+static_assert(!__is_invocable_r(void, func_arg_t, explicitly_convertible_to_int));
+static_assert(__is_invocable_r(void, func_arg_ref_t, int&));
+static_assert(!__is_invocable_r(void, func_arg_ref_t, int));
+static_assert(!__is_invocable_r(void, func_arg_ref_t, int&&));
+static_assert(!__is_invocable_r(int, func_t));
+static_assert(__is_invocable_r(int, func_return_t));
+static_assert(!__is_invocable_r(void, S));
+static_assert(__is_invocable_r(void, InvocableT));
+static_assert(!__is_invocable_r(void, InvocableT, int));
+static_assert(__is_invocable_r(void, InvocableT, int, int, T));
+static_assert(__is_invocable_r(U, InvocableT, int, int, T));
+static_assert(__is_invocable_r(int, InvocableT, int, int, T));
+static_assert(__is_invocable_r(U, InvocableT, int, int, T));
+static_assert(__is_invocable_r(V, InvocableT, int, int, T));
+static_assert(__is_invocable_r(void, InvocableT&, int, int, T));
+static_assert(!__is_invocable_r(T, InvocableT, int, int, T));
+static_assert(!__is_invocable_r(T, InvocableT&, int, int, T));
+static_assert(!__is_invocable_r(T, InvocableT&, const int&, int, T));
+static_assert(__is_invocable_r(void, InvocableT&&, T));
+static_assert(__is_invocable_r(void, StaticInvocableT));
+static_assert(!__is_invocable_r(void, StaticInvocableT, int));
+
+// __is_nothrow_invocable_r
+
+static_assert(__is_nothrow_invocable_r()); // expected-error {{expected a type}}
+static_assert(!__is_nothrow_invocable_r(Incomplete, void)); // expected-error {{incomplete type 'Incomplete' used in type trait expression}}
+static_assert(!__is_nothrow_invocable_r(void)); // expected-error {{type trait requires 2 or more arguments; have 1 argument}}
+static_assert(!__is_nothrow_invocable_r(void, int));
+
+// bullet 1
+static_assert(__is_nothrow_invocable_r(void, member_function_ptr_t, S));
+static_assert(__is_nothrow_invocable_r(void, member_function_ptr_t&, S));
+static_assert(!__is_nothrow_invocable_r(void, member_function_ptr_t&, T));
+static_assert(__is_nothrow_invocable_r(void, member_function_ptr_t, S&));
+static_assert(!__is_nothrow_invocable_r(void, member_function_ptr_t, T&));
+static_assert(!__is_nothrow_invocable_r(void, member_function_arg_ptr_t, S&, int));
+static_assert(!__is_nothrow_invocable_r(void, member_function_arg_ptr_t, S&, int&));
+static_assert(!__is_nothrow_invocable_r(void, member_function_arg_ptr_t, S&, int&));
+static_assert(!__is_nothrow_invocable_r(void, member_function_return_t, S&));
+static_assert(!__is_nothrow_invocable_r(Derived, member_function_return_t, S&));
+static_assert(!__is_nothrow_invocable_r(S, member_function_return_t, S&));
+static_assert(!__is_nothrow_invocable_r(T, member_function_return_t, S&));
+
+// bullet 2
+static_assert(__is_nothrow_invocable_r(void, member_function_ptr_t, std::reference_wrapper<S>));
+static_assert(__is_nothrow_invocable_r(void, member_function_ptr_t&, std::reference_wrapper<S>));
+static_assert(!__is_nothrow_invocable_r(void, member_function_ptr_t&, std::reference_wrapper<T>));
+static_assert(__is_nothrow_invocable_r(void, member_function_ptr_t, std::reference_wrapper<S>&));
+static_assert(!__is_nothrow_invocable_r(void, member_function_ptr_t, std::reference_wrapper<T>&));
+static_assert(!__is_nothrow_invocable_r(void, member_function_arg_ptr_t, std::reference_wrapper<S>&, int));
+static_assert(!__is_nothrow_invocable_r(void, member_function_arg_ptr_t, std::reference_wrapper<S>&, int&));
+static_assert(!__is_nothrow_invocable_r(void, member_function_arg_ptr_t, std::reference_wrapper<S>&, int&));
+static_assert(!__is_nothrow_invocable_r(void, member_function_return_t, std::reference_wrapper<S>&));
+static_assert(!__is_nothrow_invocable_r(Derived, member_function_return_t, std::reference_wrapper<S>&));
+static_assert(!__is_nothrow_invocable_r(S, member_function_return_t, std::reference_wrapper<S>&));
+static_assert(!__is_nothrow_invocable_r(T, member_function_return_t, std::reference_wrapper<S>&));
+
+// bullet 3
+static_assert(__is_nothrow_invocable_r(void, member_function_ptr_t, S*));
+static_assert(__is_nothrow_invocable_r(void, member_function_ptr_t&, S*));
+static_assert(!__is_nothrow_invocable_r(void, member_function_ptr_t&, T*));
+static_assert(__is_nothrow_invocable_r(void, member_function_ptr_t, S*&));
+static_assert(!__is_nothrow_invocable_r(void, member_function_ptr_t, T*&));
+static_assert(!__is_nothrow_invocable_r(void, member_function_arg_ptr_t, S*&, int));
+static_assert(!__is_nothrow_invocable_r(void, member_function_arg_ptr_t, S*&, int&));
+static_assert(!__is_nothrow_invocable_r(void, member_function_arg_ptr_t, S*&, int&));
+static_assert(!__is_nothrow_invocable_r(void, member_function_return_t, S*&));
+static_assert(!__is_nothrow_invocable_r(Derived, member_function_return_t, S*&));
+static_assert(!__is_nothrow_invocable_r(S, member_function_return_t, S*&));
+static_assert(!__is_nothrow_invocable_r(T, member_function_return_t, S*&));
+
+// bullet 4
+static_assert(!__is_nothrow_invocable_r(void, member_ptr_t));
+static_assert(!__is_nothrow_invocable_r(void, member_ptr_t, S&, int));
+static_assert(__is_nothrow_invocable_r(void, member_ptr_t, S&));
+static_assert(__is_nothrow_invocable_r(void, member_ptr_t&, S&));
+static_assert(!__is_nothrow_invocable_r(void, member_ptr_t, T&));
+static_assert(!__is_nothrow_invocable_r(void, member_ptr_t, T&, int));
+static_assert(__is_nothrow_invocable_r(int, member_ptr_t, S&));
+static_assert(__is_nothrow_invocable_r(long, member_ptr_t, S&));
+static_assert(!__is_nothrow_invocable_r(T, member_ptr_t, S&));
+static_assert(__is_nothrow_invocable_r(void, member_ptr_t, Derived&));
+static_assert(!__is_nothrow_invocable_r(void, member_ptr_t, Derived&, int));
+
+// bullet 5
+static_assert(!__is_nothrow_invocable_r(void, member_ptr_t, int));
+static_assert(__is_nothrow_invocable_r(void, member_ptr_t, std::reference_wrapper<S>));
+static_assert(!__is_nothrow_invocable_r(void, member_ptr_t, std::reference_wrapper<S>, int));
+static_assert(__is_nothrow_invocable_r(void, member_ptr_t, std::reference_wrapper<S>&));
+static_assert(__is_nothrow_invocable_r(void, member_ptr_t&, std::reference_wrapper<S>));
+static_assert(!__is_nothrow_invocable_r(void, member_ptr_t, std::reference_wrapper<T>));
+static_assert(__is_nothrow_invocable_r(int, member_ptr_t, std::reference_wrapper<S>));
+static_assert(__is_nothrow_invocable_r(long, member_ptr_t, std::reference_wrapper<S>));
+static_assert(!__is_nothrow_invocable_r(T, member_ptr_t, std::reference_wrapper<S>));
+static_assert(__is_nothrow_invocable_r(void, member_ptr_t, std::reference_wrapper<Derived>));
+
+// bullet 6
+static_assert(__is_nothrow_invocable_r(void, member_ptr_t, S*));
+static_assert(__is_nothrow_invocable_r(void, member_ptr_t&, S*));
+static_assert(!__is_nothrow_invocable_r(void, member_ptr_t&, S*, int));
+static_assert(!__is_nothrow_invocable_r(void, member_ptr_t, T*));
+static_assert(__is_nothrow_invocable_r(int, member_ptr_t, S*));
+static_assert(__is_nothrow_invocable_r(long, member_ptr_t, S*));
+static_assert(!__is_nothrow_invocable_r(T, member_ptr_t, S*));
+static_assert(__is_nothrow_invocable_r(void, member_ptr_t, Derived*));
+
+// Bullet 7
+static_assert(__is_nothrow_invocable_r(void, func_t));
+static_assert(__is_nothrow_invocable_r(void, func_t&));
+static_assert(!__is_nothrow_invocable_r(void, func_arg_t));
+static_assert(!__is_nothrow_invocable_r(void, func_arg_t, int));
+static_assert(!__is_nothrow_invocable_r(void, func_arg_t, int&));
+static_assert(!__is_nothrow_invocable_r(void, func_arg_t, const int&));
+static_assert(!__is_nothrow_invocable_r(void, func_arg_t, short));
+static_assert(!__is_nothrow_invocable_r(void, func_arg_t, char));
+static_assert(!__is_nothrow_invocable_r(void, func_arg_t, long));
+static_assert(!__is_nothrow_invocable_r(void, func_arg_t, double));
+static_assert(!__is_nothrow_invocable_r(void, func_arg_t, convertible_to_int));
+static_assert(!__is_nothrow_invocable_r(void, func_arg_t, explicitly_convertible_to_int));
+static_assert(__is_nothrow_invocable_r(void, func_arg_ref_t, int&));
+static_assert(!__is_nothrow_invocable_r(void, func_arg_ref_t, int));
+static_assert(!__is_nothrow_invocable_r(void, func_arg_ref_t, int&&));
+static_assert(!__is_nothrow_invocable_r(int, func_t));
+static_assert(!__is_nothrow_invocable_r(int, func_return_t));
+static_assert(!__is_nothrow_invocable_r(void, S));
+static_assert(!__is_nothrow_invocable_r(void, InvocableT));
+static_assert(!__is_nothrow_invocable_r(void, InvocableT, int));
+static_assert(__is_nothrow_invocable_r(void, InvocableT, int, int, T));
+static_assert(__is_nothrow_invocable_r(int, InvocableT, int, int, T));
+static_assert(__is_nothrow_invocable_r(U, InvocableT, int, int, T));
+static_assert(!__is_nothrow_invocable_r(V, InvocableT, int, int, T));
+static_assert(__is_nothrow_invocable_r(void, InvocableT&, int, int, T));
+static_assert(!__is_nothrow_invocable_r(T, InvocableT, int, int, T));
+static_assert(!__is_nothrow_invocable_r(T, InvocableT&, int, int, T));
+static_assert(!__is_nothrow_invocable_r(T, InvocableT&, const int&, int, T));
+static_assert(!__is_nothrow_invocable_r(void, InvocableT&&, T));
+static_assert(__is_nothrow_invocable_r(void, StaticInvocableT));
+static_assert(!__is_nothrow_invocable_r(void, StaticInvocableT, int));
diff --git a/libcxx/include/__type_traits/invoke.h b/libcxx/include/__type_traits/invoke.h
index c0487e25a0d8b7..5286d78bf60b9e 100644
--- a/libcxx/include/__type_traits/invoke.h
+++ b/libcxx/include/__type_traits/invoke.h
@@ -424,6 +424,22 @@ struct __invoke_void_return_wrapper<_Ret, true> {
 
 // is_invocable
 
+#if __has_builtin(__is_invocable_r)
+
+template <class _Fn, class... _Args>
+struct _LIBCPP_TEMPLATE_VIS is_invocable : bool_constant<__is_invocable_r(void, _Fn, _Args...)> {};
+
+template <class _Ret, class _Fn, class... _Args>
+struct _LIBCPP_TEMPLATE_VIS is_invocable_r : bool_constant<__is_invocable_r(_Ret, _Fn, _Args...)> {};
+
+template <class _Fn, class... _Args>
+inline constexpr bool is_invocable_v = __is_invocable_r(void, _Fn, _Args...);
+
+template <class _Ret, class _Fn, class... _Args>
+inline constexpr bool is_invocable_r_v = __is_invocable_r(_Ret, _Fn, _Args...);
+
+#else
+
 template <class _Fn, class... _Args>
 struct _LIBCPP_TEMPLATE_VIS is_invocable : integral_constant<bool, __invokable<_Fn, _Args...>::value> {};
 
@@ -436,8 +452,26 @@ inline constexpr bool is_invocable_v = is_invocable<_Fn, _Args...>::value;
 template <class _Ret, class _Fn, class... _Args>
 inline constexpr bool is_invocable_r_v = is_invocable_r<_Ret, _Fn, _Args...>::value;
 
+#endif // __has_builtin(__is_invocable_r)
+
 // is_nothrow_invocable
 
+#if __has_builtin(__is_nothrow_invocable_r)
+
+template <class _Fn, class... _Args>
+struct _LIBCPP_TEMPLATE_VIS is_nothrow_invocable : bool_constant<__is_nothrow_invocable_r(void, _Fn, _Args...)> {};
+
+template <class _Ret, class _Fn, class... _Args>
+struct _LIBCPP_TEMPLATE_VIS is_nothrow_invocable_r : bool_constant<__is_nothrow_invocable_r(_Ret, _Fn, _Args...)> {};
+
+template <class _Fn, class... _Args>
+inline constexpr bool is_nothrow_invocable_v = __is_nothrow_invocable_r(void, _Fn, _Args...);
+
+template <class _Ret, class _Fn, class... _Args>
+inline constexpr bool is_nothrow_invocable_r_v = __is_nothrow_invocable_r(_Ret, _Fn, _Args...);
+
+#else
+
 template <class _Fn, class... _Args>
 struct _LIBCPP_TEMPLATE_VIS is_nothrow_invocable : integral_constant<bool, __nothrow_invokable<_Fn, _Args...>::value> {
 };
@@ -452,6 +486,8 @@ inline constexpr bool is_nothrow_invocable_v = is_nothrow_invocable<_Fn, _Args..
 template <class _Ret, class _Fn, class... _Args>
 inline constexpr bool is_nothrow_invocable_r_v = is_nothrow_invocable_r<_Ret, _Fn, _Args...>::value;
 
+#endif // __has_builtin(__is_nothrow_invocable_r)
+
 template <class _Fn, class... _Args>
 struct _LIBCPP_TEMPLATE_VIS invoke_result : __invoke_of<_Fn, _Args...> {};
 



More information about the libcxx-commits mailing list