From 2c6c2b944e85178a3dcc8012dc43496f317c5fa0 Mon Sep 17 00:00:00 2001 From: Eric Fiselier Date: Wed, 2 Nov 2016 03:57:34 +0000 Subject: [PATCH] Fix __libcpp_is_constructible for source types with explicit conversion operators. Previously __libcpp_is_constructible checked the validity of reference construction using 'eat(declval())' 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(declval())'. 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(declval())` even though `int &&X(declval())` is well formed. In order to tolerate this bug the `__eat(...)` 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. git-svn-id: https://llvm.org/svn/llvm-project/libcxx/trunk@285786 91177308-0d34-0410-b5e6-96231b3b80d8 --- include/type_traits | 85 +++++++++---- .../meta.unary.prop/is_constructible.pass.cpp | 120 +++++++++++++++++- 2 files changed, 181 insertions(+), 24 deletions(-) diff --git a/include/type_traits b/include/type_traits index 0f158fdb8..a1ed19828 100644 --- a/include/type_traits +++ b/include/type_traits @@ -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 -struct _LIBCPP_TYPE_VIS_ONLY is_constructible - : public integral_constant - {}; +template +struct __libcpp_is_constructible; -#else +template +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_base_of<_RawFrom, _RawTo>, + __lazy_not<__libcpp_is_constructible<_RawTo, _From>> + >::value; +}; -#ifndef _LIBCPP_HAS_NO_VARIADICS - -// main is_constructible test +template +struct __is_invalid_lvalue_to_rvalue_cast : false_type { + static_assert(is_reference<_To>::value, "Wrong specialization"); +}; +template +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>, + __lazy_or< + is_same<_RawFrom, _RawTo>, + is_base_of<_RawTo, _RawFrom>> + >::value; +}; struct __is_constructible_helper { - template - static true_type __test_ref(_Tp); - template - static false_type __test_ref(...); + template + static void __eat(_To); + + // This overload is needed to work around a Clang bug that disallows + // static_cast(e) for non-reference-compatible types. + // Example: static_cast(declval()); + // NOTE: The static_cast implementation below is required to support + // classes with explicit conversion operators. + template (_VSTD::declval<_From>()))> + static true_type __test_cast(int); + + template (_VSTD::declval<_From>()))> + static integral_constant::value && + !__is_invalid_lvalue_to_rvalue_cast<_To, _From>::value + > __test_cast(long); + + template + static false_type __test_cast(...); template ()...))> @@ -2961,24 +2999,27 @@ struct __libcpp_is_constructible<_Tp, _A0> template struct __libcpp_is_constructible<_Tp&, _A0> : public decltype(__is_constructible_helper:: - __test_ref<_Tp&>(_VSTD::declval<_A0>())) + __test_cast<_Tp&, _A0>(0)) {}; template 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 +struct _LIBCPP_TYPE_VIS_ONLY is_constructible + : public integral_constant + {}; +#elif !defined(_LIBCPP_CXX03_LANG) template struct _LIBCPP_TYPE_VIS_ONLY is_constructible : public __libcpp_is_constructible<_Tp, _Args...>::type {}; - - -#else // _LIBCPP_HAS_NO_VARIADICS - +#else // template struct is_constructible0; // main is_constructible0 test @@ -3151,8 +3192,8 @@ struct __is_constructible2_imp : 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 _LIBCPP_CONSTEXPR bool is_constructible_v diff --git a/test/std/utilities/meta/meta.unary/meta.unary.prop/is_constructible.pass.cpp b/test/std/utilities/meta/meta.unary/meta.unary.prop/is_constructible.pass.cpp index 7f5ab7214..eb942a52a 100644 --- a/test/std/utilities/meta/meta.unary/meta.unary.prop/is_constructible.pass.cpp +++ b/test/std/utilities/meta/meta.unary/meta.unary.prop/is_constructible.pass.cpp @@ -14,9 +14,17 @@ // template // struct is_constructible; +#define _LIBCPP_TESTING_FALLBACK_IS_CONSTRUCTIBLE #include #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 +struct ImplicitTo { + operator To(); +}; + +#if TEST_STD_VER >= 11 +template +struct ExplicitTo { + explicit operator To (); +}; +#endif + template void test_is_constructible() { static_assert( (std::is_constructible::value), ""); + LIBCPP11_STATIC_ASSERT((std::__libcpp_is_constructible::type::value), ""); #if TEST_STD_VER > 14 static_assert( std::is_constructible_v, ""); #endif @@ -68,6 +89,7 @@ template void test_is_constructible() { static_assert(( std::is_constructible::value), ""); + LIBCPP11_STATIC_ASSERT((std::__libcpp_is_constructible::type::value), ""); #if TEST_STD_VER > 14 static_assert(( std::is_constructible_v), ""); #endif @@ -77,6 +99,7 @@ template void test_is_constructible() { static_assert(( std::is_constructible::value), ""); + LIBCPP11_STATIC_ASSERT((std::__libcpp_is_constructible::type::value), ""); #if TEST_STD_VER > 14 static_assert(( std::is_constructible_v), ""); #endif @@ -86,6 +109,7 @@ template void test_is_not_constructible() { static_assert((!std::is_constructible::value), ""); + LIBCPP11_STATIC_ASSERT((!std::__libcpp_is_constructible::type::value), ""); #if TEST_STD_VER > 14 static_assert((!std::is_constructible_v), ""); #endif @@ -95,13 +119,28 @@ template void test_is_not_constructible() { static_assert((!std::is_constructible::value), ""); + LIBCPP11_STATIC_ASSERT((!std::__libcpp_is_constructible::type::value), ""); #if TEST_STD_VER > 14 static_assert((!std::is_constructible_v), ""); #endif } +#if TEST_STD_VER >= 11 +template (std::declval()))> +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 (); test_is_constructible (); test_is_constructible (); @@ -115,6 +154,14 @@ int main() test_is_constructible (); #endif test_is_not_constructible (); + test_is_not_constructible(); + test_is_not_constructible(); + test_is_not_constructible(); + test_is_not_constructible(); + test_is_not_constructible(); + test_is_not_constructible(); + test_is_not_constructible(); + test_is_not_constructible (); test_is_not_constructible (); // LWG 2738 test_is_not_constructible (); @@ -125,10 +172,21 @@ int main() test_is_constructible(); test_is_not_constructible(); + test_is_constructible(); + test_is_constructible(); +#if TEST_STD_VER >= 11 + test_is_constructible(); + test_is_constructible(); + test_is_constructible(); +#endif + #if TEST_STD_VER >= 11 test_is_constructible(); test_is_constructible(); + test_is_constructible(); + test_is_constructible(); + test_is_not_constructible(); test_is_not_constructible(); test_is_not_constructible(); @@ -157,6 +215,64 @@ int main() test_is_not_constructible(); test_is_not_constructible(); + test_is_constructible>(); + test_is_constructible>(); + test_is_constructible>(); + test_is_constructible>(); + + test_is_not_constructible(); + test_is_not_constructible(); + test_is_constructible>(); + test_is_constructible&>(); + test_is_constructible(); + test_is_constructible&>(); + test_is_constructible>(); + test_is_constructible&>(); + test_is_constructible>(); + + test_is_constructible&>(); + test_is_constructible>(); + test_is_constructible>(); + test_is_constructible>(); + + // Binding through reference-compatible type is required to perform + // direct-initialization as described in [over.match.ref] p. 1 b. 1: + test_is_constructible>(); + test_is_constructible>(); + + static_assert(std::is_constructible>::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(declval>())' is ill-formed. + LIBCPP_STATIC_ASSERT( + clang_disallows_valid_static_cast_bug != + std::__libcpp_is_constructible>::value, ""); +#else + static_assert(clang_disallows_valid_static_cast_bug == false, ""); + LIBCPP_STATIC_ASSERT(std::__libcpp_is_constructible>::value, ""); +#endif + +#ifdef __clang__ + // FIXME Clang and GCC disagree on the validity of this expression. + test_is_constructible>(); + static_assert(std::is_constructible>::value, ""); + LIBCPP_STATIC_ASSERT( + clang_disallows_valid_static_cast_bug != + std::__libcpp_is_constructible>::value, ""); +#else + test_is_not_constructible>(); + test_is_not_constructible>(); +#endif + + // Binding through temporary behaves like copy-initialization, + // see [dcl.init.ref] p. 5, very last sub-bullet: + test_is_not_constructible>(); + test_is_not_constructible>(); + + // 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 (); test_is_not_constructible (); #endif -#endif +#endif // TEST_STD_VER >= 11 }