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.


git-svn-id: https://llvm.org/svn/llvm-project/libcxx/trunk@285786 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
Eric Fiselier
2016-11-02 03:57:34 +00:00
parent c09116009c
commit 2c6c2b944e
2 changed files with 181 additions and 24 deletions

View File

@@ -2894,26 +2894,64 @@ namespace __is_construct
struct __nat {}; 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> template <class _Tp, class... _Args>
struct _LIBCPP_TYPE_VIS_ONLY is_constructible struct __libcpp_is_constructible;
: public integral_constant<bool, __is_constructible(_Tp, _Args...)>
{};
#else 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;
};
#ifndef _LIBCPP_HAS_NO_VARIADICS template <class _To, class _From>
struct __is_invalid_lvalue_to_rvalue_cast : false_type {
// main is_constructible test 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 struct __is_constructible_helper
{ {
template <class _Tp> template <class _To>
static true_type __test_ref(_Tp); static void __eat(_To);
template <class>
static false_type __test_ref(...); // 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, template <class _Tp, class ..._Args,
class = decltype(_Tp(_VSTD::declval<_Args>()...))> class = decltype(_Tp(_VSTD::declval<_Args>()...))>
@@ -2961,24 +2999,27 @@ struct __libcpp_is_constructible<_Tp, _A0>
template <class _Tp, class _A0> template <class _Tp, class _A0>
struct __libcpp_is_constructible<_Tp&, _A0> struct __libcpp_is_constructible<_Tp&, _A0>
: public decltype(__is_constructible_helper:: : public decltype(__is_constructible_helper::
__test_ref<_Tp&>(_VSTD::declval<_A0>())) __test_cast<_Tp&, _A0>(0))
{}; {};
template <class _Tp, class _A0> template <class _Tp, class _A0>
struct __libcpp_is_constructible<_Tp&&, _A0> struct __libcpp_is_constructible<_Tp&&, _A0>
: public decltype(__is_constructible_helper:: : 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> template <class _Tp, class... _Args>
struct _LIBCPP_TYPE_VIS_ONLY is_constructible struct _LIBCPP_TYPE_VIS_ONLY is_constructible
: public __libcpp_is_constructible<_Tp, _Args...>::type {}; : public __libcpp_is_constructible<_Tp, _Args...>::type {};
#else
#else // _LIBCPP_HAS_NO_VARIADICS
// template <class T> struct is_constructible0; // template <class T> struct is_constructible0;
// main is_constructible0 test // main is_constructible0 test
@@ -3151,9 +3192,9 @@ struct __is_constructible2_imp<false, _Ap[], _A0, _A1>
: public false_type : 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) #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 template <class _Tp, class ..._Args> _LIBCPP_CONSTEXPR bool is_constructible_v
= is_constructible<_Tp, _Args...>::value; = is_constructible<_Tp, _Args...>::value;

View File

@@ -14,9 +14,17 @@
// template <class T, class... Args> // template <class T, class... Args>
// struct is_constructible; // struct is_constructible;
#define _LIBCPP_TESTING_FALLBACK_IS_CONSTRUCTIBLE
#include <type_traits> #include <type_traits>
#include "test_macros.h" #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 struct A
{ {
explicit A(int); explicit A(int);
@@ -51,14 +59,27 @@ struct S {
#if TEST_STD_VER >= 11 #if TEST_STD_VER >= 11
explicit explicit
#endif #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> template <class T>
void test_is_constructible() void test_is_constructible()
{ {
static_assert( (std::is_constructible<T>::value), ""); static_assert( (std::is_constructible<T>::value), "");
LIBCPP11_STATIC_ASSERT((std::__libcpp_is_constructible<T>::type::value), "");
#if TEST_STD_VER > 14 #if TEST_STD_VER > 14
static_assert( std::is_constructible_v<T>, ""); static_assert( std::is_constructible_v<T>, "");
#endif #endif
@@ -68,6 +89,7 @@ template <class T, class A0>
void test_is_constructible() void test_is_constructible()
{ {
static_assert(( std::is_constructible<T, A0>::value), ""); static_assert(( std::is_constructible<T, A0>::value), "");
LIBCPP11_STATIC_ASSERT((std::__libcpp_is_constructible<T, A0>::type::value), "");
#if TEST_STD_VER > 14 #if TEST_STD_VER > 14
static_assert(( std::is_constructible_v<T, A0>), ""); static_assert(( std::is_constructible_v<T, A0>), "");
#endif #endif
@@ -77,6 +99,7 @@ template <class T, class A0, class A1>
void test_is_constructible() void test_is_constructible()
{ {
static_assert(( std::is_constructible<T, A0, A1>::value), ""); 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 #if TEST_STD_VER > 14
static_assert(( std::is_constructible_v<T, A0, A1>), ""); static_assert(( std::is_constructible_v<T, A0, A1>), "");
#endif #endif
@@ -86,6 +109,7 @@ template <class T>
void test_is_not_constructible() void test_is_not_constructible()
{ {
static_assert((!std::is_constructible<T>::value), ""); static_assert((!std::is_constructible<T>::value), "");
LIBCPP11_STATIC_ASSERT((!std::__libcpp_is_constructible<T>::type::value), "");
#if TEST_STD_VER > 14 #if TEST_STD_VER > 14
static_assert((!std::is_constructible_v<T>), ""); static_assert((!std::is_constructible_v<T>), "");
#endif #endif
@@ -95,13 +119,28 @@ template <class T, class A0>
void test_is_not_constructible() void test_is_not_constructible()
{ {
static_assert((!std::is_constructible<T, A0>::value), ""); static_assert((!std::is_constructible<T, A0>::value), "");
LIBCPP11_STATIC_ASSERT((!std::__libcpp_is_constructible<T, A0>::type::value), "");
#if TEST_STD_VER > 14 #if TEST_STD_VER > 14
static_assert((!std::is_constructible_v<T, A0>), ""); static_assert((!std::is_constructible_v<T, A0>), "");
#endif #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() int main()
{ {
typedef Base B;
typedef Derived D;
test_is_constructible<int> (); test_is_constructible<int> ();
test_is_constructible<int, const int> (); test_is_constructible<int, const int> ();
test_is_constructible<A, int> (); test_is_constructible<A, int> ();
@@ -115,6 +154,14 @@ int main()
test_is_constructible<A, char> (); test_is_constructible<A, char> ();
#endif #endif
test_is_not_constructible<A, void> (); 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<void> ();
test_is_not_constructible<const void> (); // LWG 2738 test_is_not_constructible<const void> (); // LWG 2738
test_is_not_constructible<volatile void> (); test_is_not_constructible<volatile void> ();
@@ -125,10 +172,21 @@ int main()
test_is_constructible<int, S>(); test_is_constructible<int, S>();
test_is_not_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 #if TEST_STD_VER >= 11
test_is_constructible<int const&, int>(); test_is_constructible<int const&, int>();
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>();
test_is_not_constructible<int&, int const&>(); test_is_not_constructible<int&, int const&>();
test_is_not_constructible<int&, int&&>(); 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() const>();
test_is_not_constructible<void() const, void*>(); 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. // TODO: Remove this workaround once Clang <= 3.7 are no longer used regularly.
// In those compiler versions the __is_constructible builtin gives the wrong // In those compiler versions the __is_constructible builtin gives the wrong
// results for abominable function types. // results for abominable function types.
@@ -171,5 +287,5 @@ int main()
test_is_not_constructible<void() &> (); test_is_not_constructible<void() &> ();
test_is_not_constructible<void() &&> (); test_is_not_constructible<void() &&> ();
#endif #endif
#endif #endif // TEST_STD_VER >= 11
} }