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 } _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits