[libcxx] r285786 - Fix __libcpp_is_constructible for source types with explicit conversion operators.

Eric Fiselier via cfe-commits cfe-commits at lists.llvm.org
Tue Nov 1 20:57:34 PDT 2016


Author: ericwf
Date: Tue Nov  1 22:57:34 2016
New Revision: 285786

URL: http://llvm.org/viewvc/llvm-project?rev=285786&view=rev
Log:
Fix __libcpp_is_constructible for source types with explicit conversion operators.

Previously __libcpp_is_constructible checked the validity of reference
construction using 'eat<To>(declval<From>())' but this doesn't consider
From's explicit conversion operators. This patch teaches __libcpp_is_constructible
how to handle these cases. To do this we need to check the validity
using 'static_cast<To>(declval<From>())'. Unfortunately static_cast allows
additional base-to-derived and lvalue-to-rvalue conversions, which have to be
checked for and manually rejected.

While implementing these changes I discovered that Clang incorrectly
rejects `static_cast<int&&>(declval<float&>())` even though
`int &&X(declval<float&>())` is well formed. In order to tolerate this bug
the `__eat<T>(...)` needs to be left in-place. Otherwise it could be replaced
entirely with the new static_cast implementation.

Thanks to Walter Brown for providing the test cases.

Modified:
    libcxx/trunk/include/type_traits
    libcxx/trunk/test/std/utilities/meta/meta.unary/meta.unary.prop/is_constructible.pass.cpp

Modified: libcxx/trunk/include/type_traits
URL: http://llvm.org/viewvc/llvm-project/libcxx/trunk/include/type_traits?rev=285786&r1=285785&r2=285786&view=diff
==============================================================================
--- libcxx/trunk/include/type_traits (original)
+++ libcxx/trunk/include/type_traits Tue Nov  1 22:57:34 2016
@@ -2894,26 +2894,64 @@ namespace __is_construct
 struct __nat {};
 }
 
-#if __has_feature(is_constructible)
+#if !defined(_LIBCPP_CXX03_LANG) && (!__has_feature(is_constructible) || \
+    defined(_LIBCPP_TESTING_FALLBACK_IS_CONSTRUCTIBLE))
 
-template <class _Tp, class ..._Args>
-struct _LIBCPP_TYPE_VIS_ONLY is_constructible
-    : public integral_constant<bool, __is_constructible(_Tp, _Args...)>
-    {};
-
-#else
+template <class _Tp, class... _Args>
+struct __libcpp_is_constructible;
 
-#ifndef _LIBCPP_HAS_NO_VARIADICS
+template <class _To, class _From>
+struct __is_invalid_base_to_derived_cast {
+  static_assert(is_reference<_To>::value, "Wrong specialization");
+  using _RawFrom = __uncvref_t<_From>;
+  using _RawTo = __uncvref_t<_To>;
+  static const bool value = __lazy_and<
+        __lazy_not<is_same<_RawFrom, _RawTo>>,
+        is_base_of<_RawFrom, _RawTo>,
+        __lazy_not<__libcpp_is_constructible<_RawTo, _From>>
+  >::value;
+};
 
-//      main is_constructible test
+template <class _To, class _From>
+struct __is_invalid_lvalue_to_rvalue_cast : false_type {
+  static_assert(is_reference<_To>::value, "Wrong specialization");
+};
 
+template <class _ToRef, class _FromRef>
+struct __is_invalid_lvalue_to_rvalue_cast<_ToRef&&, _FromRef&> {
+  using _RawFrom = __uncvref_t<_FromRef>;
+  using _RawTo = __uncvref_t<_ToRef>;
+  static const bool value = __lazy_and<
+      __lazy_not<is_function<_RawTo>>,
+      __lazy_or<
+        is_same<_RawFrom, _RawTo>,
+        is_base_of<_RawTo, _RawFrom>>
+    >::value;
+};
 
 struct __is_constructible_helper
 {
-    template <class _Tp>
-    static true_type __test_ref(_Tp);
-    template <class>
-    static false_type __test_ref(...);
+    template <class _To>
+    static void __eat(_To);
+
+    // This overload is needed to work around a Clang bug that disallows
+    // static_cast<T&&>(e) for non-reference-compatible types.
+    // Example: static_cast<int&&>(declval<double>());
+    // NOTE: The static_cast implementation below is required to support
+    //  classes with explicit conversion operators.
+    template <class _To, class _From,
+              class = decltype(__eat<_To>(_VSTD::declval<_From>()))>
+    static true_type __test_cast(int);
+
+    template <class _To, class _From,
+              class = decltype(static_cast<_To>(_VSTD::declval<_From>()))>
+    static integral_constant<bool,
+        !__is_invalid_base_to_derived_cast<_To, _From>::value &&
+        !__is_invalid_lvalue_to_rvalue_cast<_To, _From>::value
+    > __test_cast(long);
+
+    template <class, class>
+    static false_type __test_cast(...);
 
     template <class _Tp, class ..._Args,
         class = decltype(_Tp(_VSTD::declval<_Args>()...))>
@@ -2961,24 +2999,27 @@ struct __libcpp_is_constructible<_Tp, _A
 template <class _Tp, class _A0>
 struct __libcpp_is_constructible<_Tp&, _A0>
     : public decltype(__is_constructible_helper::
-    __test_ref<_Tp&>(_VSTD::declval<_A0>()))
+    __test_cast<_Tp&, _A0>(0))
 {};
 
 template <class _Tp, class _A0>
 struct __libcpp_is_constructible<_Tp&&, _A0>
     : public decltype(__is_constructible_helper::
-    __test_ref<_Tp&&>(_VSTD::declval<_A0>()))
+    __test_cast<_Tp&&, _A0>(0))
 {};
 
-//      is_constructible entry point
+#endif
 
+#if __has_feature(is_constructible)
+template <class _Tp, class ..._Args>
+struct _LIBCPP_TYPE_VIS_ONLY is_constructible
+    : public integral_constant<bool, __is_constructible(_Tp, _Args...)>
+    {};
+#elif !defined(_LIBCPP_CXX03_LANG)
 template <class _Tp, class... _Args>
 struct _LIBCPP_TYPE_VIS_ONLY is_constructible
     : public __libcpp_is_constructible<_Tp, _Args...>::type {};
-
-
-#else  // _LIBCPP_HAS_NO_VARIADICS
-
+#else
 // template <class T> struct is_constructible0;
 
 //      main is_constructible0 test
@@ -3151,8 +3192,8 @@ struct __is_constructible2_imp<false, _A
     : public false_type
     {};
 
-#endif  // _LIBCPP_HAS_NO_VARIADICS
-#endif  // __has_feature(is_constructible)
+#endif // __has_feature(is_constructible)
+
 
 #if _LIBCPP_STD_VER > 14 && !defined(_LIBCPP_HAS_NO_VARIABLE_TEMPLATES) && !defined(_LIBCPP_HAS_NO_VARIADICS)
 template <class _Tp, class ..._Args> _LIBCPP_CONSTEXPR bool is_constructible_v

Modified: libcxx/trunk/test/std/utilities/meta/meta.unary/meta.unary.prop/is_constructible.pass.cpp
URL: http://llvm.org/viewvc/llvm-project/libcxx/trunk/test/std/utilities/meta/meta.unary/meta.unary.prop/is_constructible.pass.cpp?rev=285786&r1=285785&r2=285786&view=diff
==============================================================================
--- libcxx/trunk/test/std/utilities/meta/meta.unary/meta.unary.prop/is_constructible.pass.cpp (original)
+++ libcxx/trunk/test/std/utilities/meta/meta.unary/meta.unary.prop/is_constructible.pass.cpp Tue Nov  1 22:57:34 2016
@@ -14,9 +14,17 @@
 // template <class T, class... Args>
 //   struct is_constructible;
 
+#define _LIBCPP_TESTING_FALLBACK_IS_CONSTRUCTIBLE
 #include <type_traits>
 #include "test_macros.h"
 
+#if TEST_STD_VER >= 11 && defined(_LIBCPP_VERSION)
+#define LIBCPP11_STATIC_ASSERT(...) static_assert(__VA_ARGS__)
+#else
+#define LIBCPP11_STATIC_ASSERT(...) ((void)0)
+#endif
+
+
 struct A
 {
     explicit A(int);
@@ -51,14 +59,27 @@ struct S {
 #if TEST_STD_VER >= 11
    explicit
 #endif
-   operator T () const { return T(); }
+   operator T () const;
+};
+
+template <class To>
+struct ImplicitTo {
+  operator To();
+};
+
+#if TEST_STD_VER >= 11
+template <class To>
+struct ExplicitTo {
+   explicit operator To ();
 };
+#endif
 
 
 template <class T>
 void test_is_constructible()
 {
     static_assert( (std::is_constructible<T>::value), "");
+    LIBCPP11_STATIC_ASSERT((std::__libcpp_is_constructible<T>::type::value), "");
 #if TEST_STD_VER > 14
     static_assert( std::is_constructible_v<T>, "");
 #endif
@@ -68,6 +89,7 @@ template <class T, class A0>
 void test_is_constructible()
 {
     static_assert(( std::is_constructible<T, A0>::value), "");
+    LIBCPP11_STATIC_ASSERT((std::__libcpp_is_constructible<T, A0>::type::value), "");
 #if TEST_STD_VER > 14
     static_assert(( std::is_constructible_v<T, A0>), "");
 #endif
@@ -77,6 +99,7 @@ template <class T, class A0, class A1>
 void test_is_constructible()
 {
     static_assert(( std::is_constructible<T, A0, A1>::value), "");
+    LIBCPP11_STATIC_ASSERT((std::__libcpp_is_constructible<T, A0, A1>::type::value), "");
 #if TEST_STD_VER > 14
     static_assert(( std::is_constructible_v<T, A0, A1>), "");
 #endif
@@ -86,6 +109,7 @@ template <class T>
 void test_is_not_constructible()
 {
     static_assert((!std::is_constructible<T>::value), "");
+    LIBCPP11_STATIC_ASSERT((!std::__libcpp_is_constructible<T>::type::value), "");
 #if TEST_STD_VER > 14
     static_assert((!std::is_constructible_v<T>), "");
 #endif
@@ -95,13 +119,28 @@ template <class T, class A0>
 void test_is_not_constructible()
 {
     static_assert((!std::is_constructible<T, A0>::value), "");
+    LIBCPP11_STATIC_ASSERT((!std::__libcpp_is_constructible<T, A0>::type::value), "");
 #if TEST_STD_VER > 14
     static_assert((!std::is_constructible_v<T, A0>), "");
 #endif
 }
 
+#if TEST_STD_VER >= 11
+template <class T = int, class = decltype(static_cast<T&&>(std::declval<double&>()))>
+constexpr bool  clang_disallows_valid_static_cast_test(int) { return false; };
+
+constexpr bool clang_disallows_valid_static_cast_test(long) { return true; }
+
+static constexpr bool clang_disallows_valid_static_cast_bug =
+    clang_disallows_valid_static_cast_test(0);
+#endif
+
+
 int main()
 {
+    typedef Base B;
+    typedef Derived D;
+
     test_is_constructible<int> ();
     test_is_constructible<int, const int> ();
     test_is_constructible<A, int> ();
@@ -115,6 +154,14 @@ int main()
     test_is_constructible<A, char> ();
 #endif
     test_is_not_constructible<A, void> ();
+    test_is_not_constructible<int, void()>();
+    test_is_not_constructible<int, void(&)()>();
+    test_is_not_constructible<int, void() const>();
+    test_is_not_constructible<int&, void>();
+    test_is_not_constructible<int&, void()>();
+    test_is_not_constructible<int&, void() const>();
+    test_is_not_constructible<int&, void(&)()>();
+
     test_is_not_constructible<void> ();
     test_is_not_constructible<const void> ();  // LWG 2738
     test_is_not_constructible<volatile void> ();
@@ -125,10 +172,21 @@ int main()
     test_is_constructible<int, S>();
     test_is_not_constructible<int&, S>();
 
+    test_is_constructible<void(&)(), void(&)()>();
+    test_is_constructible<void(&)(), void()>();
+#if TEST_STD_VER >= 11
+    test_is_constructible<void(&&)(), void(&&)()>();
+    test_is_constructible<void(&&)(), void()>();
+    test_is_constructible<void(&&)(), void(&)()>();
+#endif
+
 #if TEST_STD_VER >= 11
     test_is_constructible<int const&, int>();
     test_is_constructible<int const&, int&&>();
 
+    test_is_constructible<int&&, double&>();
+    test_is_constructible<void(&)(), void(&&)()>();
+
     test_is_not_constructible<int&, int>();
     test_is_not_constructible<int&, int const&>();
     test_is_not_constructible<int&, int&&>();
@@ -157,6 +215,64 @@ int main()
     test_is_not_constructible<void() const, void() const>();
     test_is_not_constructible<void() const, void*>();
 
+    test_is_constructible<int&, ImplicitTo<int&>>();
+    test_is_constructible<const int&, ImplicitTo<int&&>>();
+    test_is_constructible<int&&, ImplicitTo<int&&>>();
+    test_is_constructible<const int&, ImplicitTo<int>>();
+
+    test_is_not_constructible<B&&, B&>();
+    test_is_not_constructible<B&&, D&>();
+    test_is_constructible<B&&, ImplicitTo<D&&>>();
+    test_is_constructible<B&&, ImplicitTo<D&&>&>();
+    test_is_constructible<int&&, double&>();
+    test_is_constructible<const int&, ImplicitTo<int&>&>();
+    test_is_constructible<const int&, ImplicitTo<int&>>();
+    test_is_constructible<const int&, ExplicitTo<int&>&>();
+    test_is_constructible<const int&, ExplicitTo<int&>>();
+
+    test_is_constructible<const int&, ExplicitTo<int&>&>();
+    test_is_constructible<const int&, ExplicitTo<int&>>();
+    test_is_constructible<int&, ExplicitTo<int&>>();
+    test_is_constructible<const int&, ExplicitTo<int&&>>();
+
+    // Binding through reference-compatible type is required to perform
+    // direct-initialization as described in [over.match.ref] p. 1 b. 1:
+    test_is_constructible<int&, ExplicitTo<int&>>();
+    test_is_constructible<const int&, ExplicitTo<int&&>>();
+
+    static_assert(std::is_constructible<int&&, ExplicitTo<int&&>>::value, "");
+#ifdef __clang__
+#if defined(CLANG_TEST_VER) && CLANG_TEST_VER < 400
+    static_assert(clang_disallows_valid_static_cast_bug, "bug still exists");
+#endif
+    // FIXME Clang disallows this construction because it thinks that
+    // 'static_cast<int&&>(declval<ExplicitTo<int&&>>())' is ill-formed.
+    LIBCPP_STATIC_ASSERT(
+        clang_disallows_valid_static_cast_bug !=
+        std::__libcpp_is_constructible<int&&, ExplicitTo<int&&>>::value, "");
+#else
+    static_assert(clang_disallows_valid_static_cast_bug == false, "");
+    LIBCPP_STATIC_ASSERT(std::__libcpp_is_constructible<int&&, ExplicitTo<int&&>>::value, "");
+#endif
+
+#ifdef __clang__
+    // FIXME Clang and GCC disagree on the validity of this expression.
+    test_is_constructible<const int&, ExplicitTo<int>>();
+    static_assert(std::is_constructible<int&&, ExplicitTo<int>>::value, "");
+    LIBCPP_STATIC_ASSERT(
+        clang_disallows_valid_static_cast_bug !=
+        std::__libcpp_is_constructible<int&&, ExplicitTo<int>>::value, "");
+#else
+    test_is_not_constructible<const int&, ExplicitTo<int>>();
+    test_is_not_constructible<int&&, ExplicitTo<int>>();
+#endif
+
+    // Binding through temporary behaves like copy-initialization,
+    // see [dcl.init.ref] p. 5, very last sub-bullet:
+    test_is_not_constructible<const int&, ExplicitTo<double&&>>();
+    test_is_not_constructible<int&&, ExplicitTo<double&&>>();
+
+
 // TODO: Remove this workaround once Clang <= 3.7 are no longer used regularly.
 // In those compiler versions the __is_constructible builtin gives the wrong
 // results for abominable function types.
@@ -171,5 +287,5 @@ int main()
     test_is_not_constructible<void() &> ();
     test_is_not_constructible<void() &&> ();
 #endif
-#endif
+#endif // TEST_STD_VER >= 11
 }




More information about the cfe-commits mailing list