rsmith created this revision.
rsmith added a reviewer: aaron.ballman.
Herald added subscribers: cfe-commits, kristina, jfb.
Herald added a project: clang.

Previously __has_builtin(__builtin_*) would return false for
__builtin_*s that we modeled as keywords rather than as functions
(because they take type arguments). With this patch, all builtins
that are called with function-call-like syntax return true from
__has_builtin (covering __builtin_* and also the __is_* and __has_* type
traits and the handful of similar builtins without such a prefix).

Update the documentation on __has_builtin and on type traits to match.
While doing this I noticed the type trait documentation was out of date
and incomplete; that's fixed here too.


Repository:
  rC Clang

https://reviews.llvm.org/D66100

Files:
  docs/LanguageExtensions.rst
  include/clang/Basic/Features.def
  include/clang/Basic/TokenKinds.def
  lib/Lex/PPMacroExpansion.cpp
  test/Preprocessor/feature_tests.c
  test/Preprocessor/feature_tests.cpp

Index: test/Preprocessor/feature_tests.cpp
===================================================================
--- /dev/null
+++ test/Preprocessor/feature_tests.cpp
@@ -0,0 +1,43 @@
+// RUN: %clang_cc1 %s -triple=i686-apple-darwin9 -verify -DVERIFY
+// expected-no-diagnostics
+
+#ifndef __has_feature
+#error Should have __has_feature
+#endif
+
+#if __has_feature(something_we_dont_have)
+#error Bad
+#endif
+
+#if  !__has_builtin(__builtin_huge_val) || \
+     !__has_builtin(__builtin_shufflevector) || \
+     !__has_builtin(__builtin_convertvector) || \
+     !__has_builtin(__builtin_trap) || \
+     !__has_builtin(__c11_atomic_init) || \
+     !__has_builtin(__builtin_launder) || \
+     !__has_feature(attribute_analyzer_noreturn) || \
+     !__has_feature(attribute_overloadable)
+#error Clang should have these
+#endif
+
+// These are technically implemented as keywords, but __has_builtin should
+// still return true.
+#if !__has_builtin(__builtin_LINE) || \
+    !__has_builtin(__builtin_FILE) || \
+    !__has_builtin(__builtin_FUNCTION) || \
+    !__has_builtin(__builtin_COLUMN) || \
+    !__has_builtin(__array_rank) || \
+    !__has_builtin(__underlying_type) || \
+    !__has_builtin(__is_trivial) || \
+    !__has_builtin(__has_unique_object_representations)
+#error Clang should have these
+#endif
+
+// This is a C-only builtin.
+#if __has_builtin(__builtin_types_compatible_p)
+#error Clang should not have this in C++ mode
+#endif
+
+#if __has_builtin(__builtin_insanity)
+#error Clang should not have this
+#endif
Index: test/Preprocessor/feature_tests.c
===================================================================
--- test/Preprocessor/feature_tests.c
+++ test/Preprocessor/feature_tests.c
@@ -25,10 +25,18 @@
 #if !__has_builtin(__builtin_LINE) || \
     !__has_builtin(__builtin_FILE) || \
     !__has_builtin(__builtin_FUNCTION) || \
-    !__has_builtin(__builtin_COLUMN)
+    !__has_builtin(__builtin_COLUMN) || \
+    !__has_builtin(__builtin_types_compatible_p)
 #error Clang should have these
 #endif
 
+// These are C++-only builtins.
+#if __has_builtin(__array_rank) || \
+    __has_builtin(__underlying_type) || \
+    __has_builtin(__is_trivial)
+#error Clang should not have these in C mode
+#endif
+
 #if __has_builtin(__builtin_insanity)
 #error Clang should not have this
 #endif
Index: lib/Lex/PPMacroExpansion.cpp
===================================================================
--- lib/Lex/PPMacroExpansion.cpp
+++ lib/Lex/PPMacroExpansion.cpp
@@ -1617,21 +1617,38 @@
             return true;
           }
           return true;
+        } else if (II->getTokenID() != tok::identifier ||
+                   II->hasRevertedTokenIDToIdentifier()) {
+          // Treat all keywords that introduce a custom syntax of the form
+          //
+          //   '__some_keyword' '(' [...] ')'
+          //
+          // as being "builtin functions", even if the syntax isn't a valid
+          // function call (for example, because the builtin takes a type
+          // argument).
+          if (II->getName().startswith("__builtin_") ||
+              II->getName().startswith("__is_") ||
+              II->getName().startswith("__has_"))
+            return true;
+          return llvm::StringSwitch<bool>(II->getName())
+              .Case("__array_rank", true)
+              .Case("__array_extent", true)
+              .Case("__reference_binds_to_temporary", true)
+              .Case("__underlying_type", true)
+              .Default(false);
         } else {
           return llvm::StringSwitch<bool>(II->getName())
-                      .Case("__make_integer_seq", LangOpts.CPlusPlus)
-                      .Case("__type_pack_element", LangOpts.CPlusPlus)
-                      .Case("__builtin_available", true)
-                      .Case("__is_target_arch", true)
-                      .Case("__is_target_vendor", true)
-                      .Case("__is_target_os", true)
-                      .Case("__is_target_environment", true)
-                      .Case("__builtin_LINE", true)
-                      .Case("__builtin_FILE", true)
-                      .Case("__builtin_FUNCTION", true)
-                      .Case("__builtin_COLUMN", true)
-                      .Case("__builtin_bit_cast", true)
-                      .Default(false);
+              // Report builtin templates as being builtins.
+              .Case("__make_integer_seq", LangOpts.CPlusPlus)
+              .Case("__type_pack_element", LangOpts.CPlusPlus)
+              // Likewise for some builtin preprocessor macros.
+              // FIXME: This is inconsistent; we usually suggest detecting
+              // builtin macros via #ifdef. Don't add more cases here.
+              .Case("__is_target_arch", true)
+              .Case("__is_target_vendor", true)
+              .Case("__is_target_os", true)
+              .Case("__is_target_environment", true)
+              .Default(false);
         }
       });
   } else if (II == Ident__is_identifier) {
Index: include/clang/Basic/TokenKinds.def
===================================================================
--- include/clang/Basic/TokenKinds.def
+++ include/clang/Basic/TokenKinds.def
@@ -449,16 +449,18 @@
 // MSVC14.0 / VS2015 Type Traits
 TYPE_TRAIT_2(__is_assignable, IsAssignable, KEYCXX)
 
+// MSVC Type Traits of unknown vintage
+TYPE_TRAIT_1(__has_nothrow_move_assign, HasNothrowMoveAssign, KEYCXX)
+TYPE_TRAIT_1(__has_trivial_move_assign, HasTrivialMoveAssign, KEYCXX)
+TYPE_TRAIT_1(__has_trivial_move_constructor, HasTrivialMoveConstructor, KEYCXX)
+
 // GNU and MS Type Traits
 TYPE_TRAIT_1(__has_nothrow_assign, HasNothrowAssign, KEYCXX)
-TYPE_TRAIT_1(__has_nothrow_move_assign, HasNothrowMoveAssign, KEYCXX)
 TYPE_TRAIT_1(__has_nothrow_copy, HasNothrowCopy, KEYCXX)
 TYPE_TRAIT_1(__has_nothrow_constructor, HasNothrowConstructor, KEYCXX)
 TYPE_TRAIT_1(__has_trivial_assign, HasTrivialAssign, KEYCXX)
-TYPE_TRAIT_1(__has_trivial_move_assign, HasTrivialMoveAssign, KEYCXX)
 TYPE_TRAIT_1(__has_trivial_copy, HasTrivialCopy, KEYCXX)
 TYPE_TRAIT_1(__has_trivial_constructor, HasTrivialDefaultConstructor, KEYCXX)
-TYPE_TRAIT_1(__has_trivial_move_constructor, HasTrivialMoveConstructor, KEYCXX)
 TYPE_TRAIT_1(__has_trivial_destructor, HasTrivialDestructor, KEYCXX)
 TYPE_TRAIT_1(__has_virtual_destructor, HasVirtualDestructor, KEYCXX)
 TYPE_TRAIT_1(__is_abstract, IsAbstract, KEYCXX)
@@ -475,17 +477,18 @@
 ALIAS("__is_literal_type", __is_literal, KEYCXX)
 TYPE_TRAIT_1(__is_pod, IsPOD, KEYCXX)
 TYPE_TRAIT_1(__is_polymorphic, IsPolymorphic, KEYCXX)
+TYPE_TRAIT_1(__is_standard_layout, IsStandardLayout, KEYCXX)
 TYPE_TRAIT_1(__is_trivial, IsTrivial, KEYCXX)
+TYPE_TRAIT_2(__is_trivially_assignable, IsTriviallyAssignable, KEYCXX)
+TYPE_TRAIT_N(__is_trivially_constructible, IsTriviallyConstructible, KEYCXX)
+TYPE_TRAIT_1(__is_trivially_copyable, IsTriviallyCopyable, KEYCXX)
 TYPE_TRAIT_1(__is_union, IsUnion, KEYCXX)
 TYPE_TRAIT_1(__has_unique_object_representations,
              HasUniqueObjectRepresentations, KEYCXX)
+KEYWORD(__underlying_type           , KEYCXX)
 
 // Clang-only C++ Type Traits
-TYPE_TRAIT_N(__is_trivially_constructible, IsTriviallyConstructible, KEYCXX)
-TYPE_TRAIT_1(__is_trivially_copyable, IsTriviallyCopyable, KEYCXX)
-TYPE_TRAIT_2(__is_trivially_assignable, IsTriviallyAssignable, KEYCXX)
 TYPE_TRAIT_2(__reference_binds_to_temporary, ReferenceBindsToTemporary, KEYCXX)
-KEYWORD(__underlying_type           , KEYCXX)
 
 // Embarcadero Expression Traits
 KEYWORD(__is_lvalue_expr            , KEYCXX)
@@ -512,7 +515,6 @@
 TYPE_TRAIT_1(__is_member_pointer, IsMemberPointer, KEYCXX)
 TYPE_TRAIT_1(__is_const, IsConst, KEYCXX)
 TYPE_TRAIT_1(__is_volatile, IsVolatile, KEYCXX)
-TYPE_TRAIT_1(__is_standard_layout, IsStandardLayout, KEYCXX)
 TYPE_TRAIT_1(__is_signed, IsSigned, KEYCXX)
 TYPE_TRAIT_1(__is_unsigned, IsUnsigned, KEYCXX)
 
Index: include/clang/Basic/Features.def
===================================================================
--- include/clang/Basic/Features.def
+++ include/clang/Basic/Features.def
@@ -187,7 +187,7 @@
 // FEATURE(raw_invocation_type, LangOpts.CPlusPlus)
 // Type traits
 // N.B. Additional type traits should not be added to the following list.
-// Instead, they should be detected by has_extension.
+// Instead, they should be detected by has_builtin.
 FEATURE(has_nothrow_assign, LangOpts.CPlusPlus)
 FEATURE(has_nothrow_copy, LangOpts.CPlusPlus)
 FEATURE(has_nothrow_constructor, LangOpts.CPlusPlus)
Index: docs/LanguageExtensions.rst
===================================================================
--- docs/LanguageExtensions.rst
+++ docs/LanguageExtensions.rst
@@ -38,7 +38,9 @@
 -----------------
 
 This function-like macro takes a single identifier argument that is the name of
-a builtin function.  It evaluates to 1 if the builtin is supported or 0 if not.
+a builtin function, a builtin pseudo-function (taking one or more type
+arguments), or a builtin template.
+It evaluates to 1 if the builtin is supported or 0 if not.
 It can be used like this:
 
 .. code-block:: c++
@@ -55,6 +57,14 @@
   #endif
   ...
 
+.. note::
+
+  Prior to Clang 10, ``__has_builtin`` could not be used to detect most builtin
+  pseudo-functions.
+
+  ``__has_builtin`` should not be used to detect support for a built-in macro;
+  use ``#ifdef`` instead.
+
 .. _langext-__has_feature-__has_extension:
 
 ``__has_feature`` and ``__has_extension``
@@ -1041,8 +1051,8 @@
 
 More information could be found `here <https://clang.llvm.org/docs/Modules.html>`_.
 
-Checks for Type Trait Primitives
-================================
+Type Trait Primitives
+=====================
 
 Type trait primitives are special builtin constant expressions that can be used
 by the standard C++ library to facilitate or simplify the implementation of
@@ -1058,75 +1068,180 @@
 
 Clang supports the `GNU C++ type traits
 <https://gcc.gnu.org/onlinedocs/gcc/Type-Traits.html>`_ and a subset of the
-`Microsoft Visual C++ Type traits
-<https://msdn.microsoft.com/en-us/library/ms177194(v=VS.100).aspx>`_.
+`Microsoft Visual C++ type traits
+<https://msdn.microsoft.com/en-us/library/ms177194(v=VS.100).aspx>`_,
+as well as nearly all of the
+`Embarcadero C++ type traits
+<http://docwiki.embarcadero.com/RADStudio/Rio/en/Type_Trait_Functions_(C%2B%2B11)_Index>`_.
 
-Feature detection is supported only for some of the primitives at present. User
-code should not use these checks because they bear no direct relation to the
-actual set of type traits supported by the C++ standard library.
+The following type trait primitives are supported by Clang. Those traits marked
+(C++) provide implementations for type traits specified by the C++ standard;
+``__X(...)`` has the same semantics and constraints as the corresponding
+``std::X_t<...>`` or ``std::X_v<...>`` type trait.
 
-For type trait ``__X``, ``__has_extension(X)`` indicates the presence of the
-type trait primitive in the compiler. A simplistic usage example as might be
-seen in standard C++ headers follows:
-
-.. code-block:: c++
-
-  #if __has_extension(is_convertible_to)
-  template<typename From, typename To>
-  struct is_convertible_to {
-    static const bool value = __is_convertible_to(From, To);
-  };
-  #else
-  // Emulate type trait for compatibility with other compilers.
-  #endif
-
-The following type trait primitives are supported by Clang:
-
-* ``__has_nothrow_assign`` (GNU, Microsoft)
-* ``__has_nothrow_copy`` (GNU, Microsoft)
-* ``__has_nothrow_constructor`` (GNU, Microsoft)
-* ``__has_trivial_assign`` (GNU, Microsoft)
-* ``__has_trivial_copy`` (GNU, Microsoft)
-* ``__has_trivial_constructor`` (GNU, Microsoft)
-* ``__has_trivial_destructor`` (GNU, Microsoft)
-* ``__has_virtual_destructor`` (GNU, Microsoft)
-* ``__is_abstract`` (GNU, Microsoft)
-* ``__is_aggregate`` (GNU, Microsoft)
-* ``__is_base_of`` (GNU, Microsoft)
-* ``__is_class`` (GNU, Microsoft)
-* ``__is_convertible_to`` (Microsoft)
-* ``__is_empty`` (GNU, Microsoft)
-* ``__is_enum`` (GNU, Microsoft)
-* ``__is_interface_class`` (Microsoft)
-* ``__is_pod`` (GNU, Microsoft)
-* ``__is_polymorphic`` (GNU, Microsoft)
-* ``__is_union`` (GNU, Microsoft)
-* ``__is_literal(type)``: Determines whether the given type is a literal type
-* ``__is_final``: Determines whether the given type is declared with a
-  ``final`` class-virt-specifier.
-* ``__underlying_type(type)``: Retrieves the underlying type for a given
-  ``enum`` type.  This trait is required to implement the C++11 standard
-  library.
-* ``__is_trivially_assignable(totype, fromtype)``: Determines whether a value
-  of type ``totype`` can be assigned to from a value of type ``fromtype`` such
-  that no non-trivial functions are called as part of that assignment.  This
-  trait is required to implement the C++11 standard library.
-* ``__is_trivially_constructible(type, argtypes...)``: Determines whether a
-  value of type ``type`` can be direct-initialized with arguments of types
-  ``argtypes...`` such that no non-trivial functions are called as part of
-  that initialization.  This trait is required to implement the C++11 standard
-  library.
-* ``__is_destructible`` (MSVC 2013)
-* ``__is_nothrow_destructible`` (MSVC 2013)
-* ``__is_nothrow_assignable`` (MSVC 2013, clang)
-* ``__is_constructible`` (MSVC 2013, clang)
-* ``__is_nothrow_constructible`` (MSVC 2013, clang)
-* ``__is_assignable`` (MSVC 2015, clang)
+* ``__array_rank(type)`` (Embarcadero):
+  Returns the number of levels of array in the type ``type``:
+  ``0`` if ``type`` is not an array type, and
+  ``__array_rank(element) + 1`` if ``type`` is an array of ``element``.
+* ``__array_extent(type, dim)`` (Embarcadero):
+  The ``dim``'th array bound in the type ``type``, or ``0`` if
+  ``dim >= __array_rank(type)``.
+* ``__has_nothrow_assign`` (GNU, Microsoft, Embarcadero):
+  Deprecated, use ``__is_nothrow_assignable`` instead.
+* ``__has_nothrow_move_assign`` (GNU, Microsoft):
+  Deprecated, use ``__is_nothrow_assignable`` instead.
+* ``__has_nothrow_copy`` (GNU, Microsoft):
+  Deprecated, use ``__is_nothrow_constructible`` instead.
+* ``__has_nothrow_constructor`` (GNU, Microsoft):
+  Deprecated, use ``__is_nothrow_constructible`` instead.
+* ``__has_trivial_assign`` (GNU, Microsoft, Embarcadero):
+  Deprecated, use ``__is_trivially_assignable`` instead.
+* ``__has_trivial_move_assign`` (GNU, Microsoft):
+  Deprecated, use ``__is_trivially_assignable`` instead.
+* ``__has_trivial_copy`` (GNU, Microsoft):
+  Deprecated, use ``__is_trivially_constructible`` instead.
+* ``__has_trivial_constructor`` (GNU, Microsoft):
+  Deprecated, use ``__is_trivially_constructible`` instead.
+* ``__has_trivial_move_constructor`` (GNU, Microsoft):
+  Deprecated, use ``__is_trivially_constructible`` instead.
+* ``__has_trivial_destructor`` (GNU, Microsoft, Embarcadero):
+  Deprecated, use ``__is_trivially_destructible`` instead.
+* ``__has_unique_object_representations`` (C++, GNU)
+* ``__has_virtual_destructor`` (C++, GNU, Microsoft, Embarcadero)
+* ``__is_abstract`` (C++, GNU, Microsoft, Embarcadero)
+* ``__is_aggregate`` (C++, GNU, Microsoft)
+* ``__is_arithmetic`` (C++, Embarcadero)
+* ``__is_array`` (C++, Embarcadero)
+* ``__is_assignable`` (C++, MSVC 2015)
+* ``__is_base_of`` (C++, GNU, Microsoft, Embarcadero)
+* ``__is_class`` (C++, GNU, Microsoft, Embarcadero)
+* ``__is_complete_type(type)`` (Embarcadero):
+  Return ``true`` if ``type`` is a complete type.
+  Warning: this trait is dangerous, because it can return different values at
+  different points in the same program.
+* ``__is_compound`` (C++, Embarcadero)
+* ``__is_const`` (C++, Embarcadero)
+* ``__is_constructible`` (C++, MSVC 2013)
+* ``__is_convertible`` (C++, Embarcadero)
+* ``__is_convertible_to`` (Microsoft):
+  Synonym for ``__is_convertible``.
+* ``__is_destructible`` (C++, MSVC 2013):
+  Only available in ``-fms-extensions`` mode.
+* ``__is_empty`` (C++, GNU, Microsoft, Embarcadero)
+* ``__is_enum`` (C++, GNU, Microsoft, Embarcadero)
+* ``__is_final`` (C++, GNU, Microsoft)
+* ``__is_floating_point`` (C++, Embarcadero)
+* ``__is_function`` (C++, Embarcadero)
+* ``__is_fundamental`` (C++, Embarcadero)
+* ``__is_integral`` (C++, Embarcadero)
+* ``__is_interface_class`` (Microsoft):
+  Returns ``false``, even for types defined with ``__interface``.
+* ``__is_literal`` (Clang):
+  Synonym for ``__is_literal_type``.
+* ``__is_literal_type`` (C++, GNU, Microsoft):
+  Note, the corresponding standard trait was deprecated in C++17
+  and removed in C++20.
+* ``__is_lvalue_reference`` (C++, Embarcadero)
+* ``__is_member_object_pointer`` (C++, Embarcadero)
+* ``__is_member_function_pointer`` (C++, Embarcadero)
+* ``__is_member_pointer`` (C++, Embarcadero)
+* ``__is_nothrow_assignable`` (C++, MSVC 2013)
+* ``__is_nothrow_constructible`` (C++, MSVC 2013)
+* ``__is_nothrow_destructible`` (C++, MSVC 2013)
+  Only available in ``-fms-extensions`` mode.
+* ``__is_object`` (C++, Embarcadero)
+* ``__is_pod`` (C++, GNU, Microsoft, Embarcadero):
+  Note, the corresponding standard trait was deprecated in C++20.
+* ``__is_pointer`` (C++, Embarcadero)
+* ``__is_polymorphic`` (C++, GNU, Microsoft, Embarcadero)
+* ``__is_reference`` (C++, Embarcadero)
+* ``__is_rvalue_reference`` (C++, Embarcadero)
+* ``__is_same`` (C++, Embarcadero)
+* ``__is_scalar`` (C++, Embarcadero)
+* ``__is_sealed`` (Microsoft):
+  Synonym for ``__is_final``.
+* ``__is_signed`` (C++, Embarcadero):
+  Note that this currently returns true for enumeration types if the underlying
+  type is signed, and returns false for floating-point types, in violation of
+  the requirements for ``std::is_signed``. This behavior is likely to change in
+  a future version of Clang.
+* ``__is_standard_layout`` (C++, GNU, Microsoft, Embarcadero)
+* ``__is_trivial`` (C++, GNU, Microsoft, Embarcadero)
+* ``__is_trivially_assignable`` (C++, GNU, Microsoft)
+* ``__is_trivially_constructible`` (C++, GNU, Microsoft)
+* ``__is_trivially_copyable`` (C++, GNU, Microsoft)
+* ``__is_trivially_destructible`` (C++, MSVC 2013)
+* ``__is_union`` (C++, GNU, Microsoft, Embarcadero)
+* ``__is_unsigned`` (C++, Embarcadero)
+  Note that this currently returns true for enumeration types if the underlying
+  type is unsigned, in violation of the requirements for ``std::is_unsigned``.
+  This behavior is likely to change in a future version of Clang.
+* ``__is_void`` (C++, Embarcadero)
+* ``__is_volatile`` (C++, Embarcadero)
 * ``__reference_binds_to_temporary(T, U)`` (Clang):  Determines whether a
   reference of type ``T`` bound to an expression of type ``U`` would bind to a
   materialized temporary object. If ``T`` is not a reference type the result
   is false. Note this trait will also return false when the initialization of
   ``T`` from ``U`` is ill-formed.
+* ``__underlying_type`` (C++, GNU, Microsoft)
+
+In addition, the following expression traits are supported:
+
+* ``__is_lvalue_expr(e)`` (Embarcadero):
+  Returns true if ``e`` is an lvalue expression.
+  Deprecated, use ``__is_lvalue_reference(decltype((e)))`` instead.
+* ``__is_rvalue_expr(e)`` (Embarcadero):
+  Returns true if ``e`` is a prvalue expression.
+  Deprecated, use ``!__is_reference(decltype((e)))`` instead.
+
+There are multiple ways to detect support for a type trait ``__X`` in the
+compiler, depending on the oldest version of Clang you wish to support.
+
+* From Clang 10 onwards, ``__has_builtin(__X)`` can be used.
+* From Clang 6 onwards, ``!__is_identifier(__X)`` can be used.
+* From Clang 3 onwards, ``__has_feature(X)`` can be used, but only supports
+  the following traits:
+
+  * ``__has_nothrow_assign``
+  * ``__has_nothrow_copy``
+  * ``__has_nothrow_constructor``
+  * ``__has_trivial_assign``
+  * ``__has_trivial_copy``
+  * ``__has_trivial_constructor``
+  * ``__has_trivial_destructor``
+  * ``__has_virtual_destructor``
+  * ``__is_abstract``
+  * ``__is_base_of``
+  * ``__is_class``
+  * ``__is_constructible``
+  * ``__is_convertible_to``
+  * ``__is_empty``
+  * ``__is_enum``
+  * ``__is_final``
+  * ``__is_literal``
+  * ``__is_standard_layout``
+  * ``__is_pod``
+  * ``__is_polymorphic``
+  * ``__is_sealed``
+  * ``__is_trivial``
+  * ``__is_trivially_assignable``
+  * ``__is_trivially_constructible``
+  * ``__is_trivially_copyable``
+  * ``__is_union``
+  * ``__underlying_type``
+
+A simplistic usage example as might be seen in standard C++ headers follows:
+
+.. code-block:: c++
+
+  #if __has_builtin(__is_convertible_to)
+  template<typename From, typename To>
+  struct is_convertible_to {
+    static const bool value = __is_convertible_to(From, To);
+  };
+  #else
+  // Emulate type trait for compatibility with other compilers.
+  #endif
 
 Blocks
 ======
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
  • [PATCH] D66100: A... Richard Smith - zygoloid via Phabricator via cfe-commits

Reply via email to