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:
@@ -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...)>
|
||||
{};
|
||||
template <class _Tp, class... _Args>
|
||||
struct __libcpp_is_constructible;
|
||||
|
||||
#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
|
||||
|
||||
// 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, _A0>
|
||||
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,9 +3192,9 @@ struct __is_constructible2_imp<false, _Ap[], _A0, _A1>
|
||||
: public false_type
|
||||
{};
|
||||
|
||||
#endif // _LIBCPP_HAS_NO_VARIADICS
|
||||
#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
|
||||
= is_constructible<_Tp, _Args...>::value;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user