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 {};
|
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;
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user