Changeset View
Standalone View
libcxx/include/tuple
Show All 19 Lines | |||||||||||||
class tuple { | class tuple { | ||||||||||||
public: | public: | ||||||||||||
explicit(see-below) constexpr tuple(); | explicit(see-below) constexpr tuple(); | ||||||||||||
explicit(see-below) tuple(const T&...); // constexpr in C++14 | explicit(see-below) tuple(const T&...); // constexpr in C++14 | ||||||||||||
template <class... U> | template <class... U> | ||||||||||||
explicit(see-below) tuple(U&&...); // constexpr in C++14 | explicit(see-below) tuple(U&&...); // constexpr in C++14 | ||||||||||||
tuple(const tuple&) = default; | tuple(const tuple&) = default; | ||||||||||||
tuple(tuple&&) = default; | tuple(tuple&&) = default; | ||||||||||||
template<class... UTypes> | |||||||||||||
constexpr explicit(see-below) tuple(tuple<UTypes...>&); // C++23 | |||||||||||||
ldionne: Nit, but let's be consistent. Here and below. | |||||||||||||
template <class... U> | template <class... U> | ||||||||||||
explicit(see-below) tuple(const tuple<U...>&); // constexpr in C++14 | explicit(see-below) tuple(const tuple<U...>&); // constexpr in C++14 | ||||||||||||
template <class... U> | template <class... U> | ||||||||||||
explicit(see-below) tuple(tuple<U...>&&); // constexpr in C++14 | explicit(see-below) tuple(tuple<U...>&&); // constexpr in C++14 | ||||||||||||
template<class... UTypes> | |||||||||||||
constexpr explicit(see-below) tuple(const tuple<UTypes...>&&); // C++23 | |||||||||||||
template<class U1, class U2> | |||||||||||||
constexpr explicit(see-below) tuple(pair<U1, U2>&); // iff sizeof...(Types) == 2 // C++23 | |||||||||||||
template <class U1, class U2> | template <class U1, class U2> | ||||||||||||
explicit(see-below) tuple(const pair<U1, U2>&); // iff sizeof...(T) == 2 // constexpr in C++14 | explicit(see-below) tuple(const pair<U1, U2>&); // iff sizeof...(T) == 2 // constexpr in C++14 | ||||||||||||
template <class U1, class U2> | template <class U1, class U2> | ||||||||||||
explicit(see-below) tuple(pair<U1, U2>&&); // iff sizeof...(T) == 2 // constexpr in C++14 | explicit(see-below) tuple(pair<U1, U2>&&); // iff sizeof...(T) == 2 // constexpr in C++14 | ||||||||||||
template<class U1, class U2> | |||||||||||||
constexpr explicit(see-below) tuple(const pair<U1, U2>&&); // iff sizeof...(Types) == 2 // C++23 | |||||||||||||
// allocator-extended constructors | // allocator-extended constructors | ||||||||||||
template <class Alloc> | template <class Alloc> | ||||||||||||
tuple(allocator_arg_t, const Alloc& a); | tuple(allocator_arg_t, const Alloc& a); | ||||||||||||
template <class Alloc> | template <class Alloc> | ||||||||||||
explicit(see-below) tuple(allocator_arg_t, const Alloc& a, const T&...); // constexpr in C++20 | explicit(see-below) tuple(allocator_arg_t, const Alloc& a, const T&...); // constexpr in C++20 | ||||||||||||
template <class Alloc, class... U> | template <class Alloc, class... U> | ||||||||||||
explicit(see-below) tuple(allocator_arg_t, const Alloc& a, U&&...); // constexpr in C++20 | explicit(see-below) tuple(allocator_arg_t, const Alloc& a, U&&...); // constexpr in C++20 | ||||||||||||
template <class Alloc> | template <class Alloc> | ||||||||||||
tuple(allocator_arg_t, const Alloc& a, const tuple&); // constexpr in C++20 | tuple(allocator_arg_t, const Alloc& a, const tuple&); // constexpr in C++20 | ||||||||||||
template <class Alloc> | template <class Alloc> | ||||||||||||
tuple(allocator_arg_t, const Alloc& a, tuple&&); // constexpr in C++20 | tuple(allocator_arg_t, const Alloc& a, tuple&&); // constexpr in C++20 | ||||||||||||
template<class Alloc, class... UTypes> | |||||||||||||
constexpr explicit(see-below) | |||||||||||||
tuple(allocator_arg_t, const Alloc& a, tuple<UTypes...>&); // C++23 | |||||||||||||
template <class Alloc, class... U> | template <class Alloc, class... U> | ||||||||||||
explicit(see-below) tuple(allocator_arg_t, const Alloc& a, const tuple<U...>&); // constexpr in C++20 | explicit(see-below) tuple(allocator_arg_t, const Alloc& a, const tuple<U...>&); // constexpr in C++20 | ||||||||||||
template <class Alloc, class... U> | template <class Alloc, class... U> | ||||||||||||
explicit(see-below) tuple(allocator_arg_t, const Alloc& a, tuple<U...>&&); // constexpr in C++20 | explicit(see-below) tuple(allocator_arg_t, const Alloc& a, tuple<U...>&&); // constexpr in C++20 | ||||||||||||
template<class Alloc, class... UTypes> | |||||||||||||
constexpr explicit(see-below) | |||||||||||||
tuple(allocator_arg_t, const Alloc& a, const tuple<UTypes...>&&); // C++23 | |||||||||||||
template<class Alloc, class U1, class U2> | |||||||||||||
constexpr explicit(see-below) | |||||||||||||
tuple(allocator_arg_t, const Alloc& a, pair<U1, U2>&); // C++23 | |||||||||||||
template <class Alloc, class U1, class U2> | template <class Alloc, class U1, class U2> | ||||||||||||
explicit(see-below) tuple(allocator_arg_t, const Alloc& a, const pair<U1, U2>&); // constexpr in C++20 | explicit(see-below) tuple(allocator_arg_t, const Alloc& a, const pair<U1, U2>&); // constexpr in C++20 | ||||||||||||
template <class Alloc, class U1, class U2> | template <class Alloc, class U1, class U2> | ||||||||||||
explicit(see-below) tuple(allocator_arg_t, const Alloc& a, pair<U1, U2>&&); // constexpr in C++20 | explicit(see-below) tuple(allocator_arg_t, const Alloc& a, pair<U1, U2>&&); // constexpr in C++20 | ||||||||||||
template<class Alloc, class U1, class U2> | |||||||||||||
constexpr explicit(see-below) | |||||||||||||
tuple(allocator_arg_t, const Alloc& a, const pair<U1, U2>&&); // C++23 | |||||||||||||
tuple& operator=(const tuple&); // constexpr in C++20 | tuple& operator=(const tuple&); // constexpr in C++20 | ||||||||||||
constexpr const tuple& operator=(const tuple&) const; // C++23 | |||||||||||||
tuple& operator=(tuple&&) noexcept(is_nothrow_move_assignable_v<T> && ...); // constexpr in C++20 | tuple& operator=(tuple&&) noexcept(is_nothrow_move_assignable_v<T> && ...); // constexpr in C++20 | ||||||||||||
constexpr const tuple& operator=(tuple&&) const; // C++23 | |||||||||||||
template <class... U> | template <class... U> | ||||||||||||
tuple& operator=(const tuple<U...>&); // constexpr in C++20 | tuple& operator=(const tuple<U...>&); // constexpr in C++20 | ||||||||||||
template<class... UTypes> | |||||||||||||
constexpr const tuple& operator=(const tuple<UTypes...>&) const; // C++23 | |||||||||||||
template <class... U> | template <class... U> | ||||||||||||
tuple& operator=(tuple<U...>&&); // constexpr in C++20 | tuple& operator=(tuple<U...>&&); // constexpr in C++20 | ||||||||||||
template<class... UTypes> | |||||||||||||
constexpr const tuple& operator=(tuple<UTypes...>&&) const; // C++23 | |||||||||||||
template <class U1, class U2> | template <class U1, class U2> | ||||||||||||
tuple& operator=(const pair<U1, U2>&); // iff sizeof...(T) == 2 // constexpr in C++20 | tuple& operator=(const pair<U1, U2>&); // iff sizeof...(T) == 2 // constexpr in C++20 | ||||||||||||
template <class U1, class U2> | template<class U1, class U2> | ||||||||||||
constexpr const tuple& operator=(const pair<U1, U2>&) const; // iff sizeof...(Types) == 2 // C++23 | |||||||||||||
template <class U1, class U2> | |||||||||||||
tuple& operator=(pair<U1, U2>&&); // iff sizeof...(T) == 2 // constexpr in C++20 | tuple& operator=(pair<U1, U2>&&); // iff sizeof...(T) == 2 // constexpr in C++20 | ||||||||||||
template<class U1, class U2> | |||||||||||||
constexpr const tuple& operator=(pair<U1, U2>&&) const; // iff sizeof...(Types) == 2 // C++23 | |||||||||||||
template<class U, size_t N> | template<class U, size_t N> | ||||||||||||
tuple& operator=(array<U, N> const&) // iff sizeof...(T) == N, EXTENSION | tuple& operator=(array<U, N> const&) // iff sizeof...(T) == N, EXTENSION | ||||||||||||
template<class U, size_t N> | template<class U, size_t N> | ||||||||||||
tuple& operator=(array<U, N>&&) // iff sizeof...(T) == N, EXTENSION | tuple& operator=(array<U, N>&&) // iff sizeof...(T) == N, EXTENSION | ||||||||||||
void swap(tuple&) noexcept(AND(swap(declval<T&>(), declval<T&>())...)); // constexpr in C++20 | void swap(tuple&) noexcept(AND(swap(declval<T&>(), declval<T&>())...)); // constexpr in C++20 | ||||||||||||
constexpr void swap(const tuple&) const noexcept(see-below); // C++23 | |||||||||||||
}; | }; | ||||||||||||
template<class... TTypes, class... UTypes, template<class> class TQual, template<class> class UQual> // since C++23 | template<class... TTypes, class... UTypes, template<class> class TQual, template<class> class UQual> // since C++23 | ||||||||||||
requires requires { typename tuple<common_reference_t<TQual<TTypes>, UQual<UTypes>>...>; } | requires requires { typename tuple<common_reference_t<TQual<TTypes>, UQual<UTypes>>...>; } | ||||||||||||
struct basic_common_reference<tuple<TTypes...>, tuple<UTypes...>, TQual, UQual> { | struct basic_common_reference<tuple<TTypes...>, tuple<UTypes...>, TQual, UQual> { | ||||||||||||
using type = tuple<common_reference_t<TQual<TTypes>, UQual<UTypes>>...>; | using type = tuple<common_reference_t<TQual<TTypes>, UQual<UTypes>>...>; | ||||||||||||
}; | }; | ||||||||||||
▲ Show 20 Lines • Show All 74 Lines • ▼ Show 20 Lines | |||||||||||||
template <class... Types, class Alloc> | template <class... Types, class Alloc> | ||||||||||||
struct uses_allocator<tuple<Types...>, Alloc>; | struct uses_allocator<tuple<Types...>, Alloc>; | ||||||||||||
template <class... Types> | template <class... Types> | ||||||||||||
void | void | ||||||||||||
swap(tuple<Types...>& x, tuple<Types...>& y) noexcept(noexcept(x.swap(y))); | swap(tuple<Types...>& x, tuple<Types...>& y) noexcept(noexcept(x.swap(y))); | ||||||||||||
template <class... Types> | |||||||||||||
constexpr void swap(const tuple<Types...>& x, const tuple<Types...>& y) noexcept(see-below); // C++23 | |||||||||||||
} // std | } // std | ||||||||||||
*/ | */ | ||||||||||||
#include <__assert> // all public C++ headers provide the assertion handler | #include <__assert> // all public C++ headers provide the assertion handler | ||||||||||||
#include <__compare/common_comparison_category.h> | #include <__compare/common_comparison_category.h> | ||||||||||||
#include <__compare/synth_three_way.h> | #include <__compare/synth_three_way.h> | ||||||||||||
#include <__config> | #include <__config> | ||||||||||||
Show All 33 Lines | |||||||||||||
template <size_t _Ip, class _Hp, bool _Ep> | template <size_t _Ip, class _Hp, bool _Ep> | ||||||||||||
inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 | inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 | ||||||||||||
void swap(__tuple_leaf<_Ip, _Hp, _Ep>& __x, __tuple_leaf<_Ip, _Hp, _Ep>& __y) | void swap(__tuple_leaf<_Ip, _Hp, _Ep>& __x, __tuple_leaf<_Ip, _Hp, _Ep>& __y) | ||||||||||||
_NOEXCEPT_(__is_nothrow_swappable<_Hp>::value) | _NOEXCEPT_(__is_nothrow_swappable<_Hp>::value) | ||||||||||||
{ | { | ||||||||||||
swap(__x.get(), __y.get()); | swap(__x.get(), __y.get()); | ||||||||||||
} | } | ||||||||||||
template <size_t _Ip, class _Hp, bool _Ep> | |||||||||||||
I would *not* guard this with _LIBCPP_STD_VER since this is internal. We can guard the top-level swap on std::tuple only. ldionne: I would *not* guard this with `_LIBCPP_STD_VER` since this is internal. We can guard the top… | |||||||||||||
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_AFTER_CXX11 | |||||||||||||
void swap(const __tuple_leaf<_Ip, _Hp, _Ep>& __x, const __tuple_leaf<_Ip, _Hp, _Ep>& __y) | |||||||||||||
Since this is new code, I would recommend not using inline here. ldionne: Since this is new code, I would recommend not using `inline` here. | |||||||||||||
_NOEXCEPT_(__is_nothrow_swappable<const _Hp>::value) { | |||||||||||||
swap(__x.get(), __y.get()); | |||||||||||||
} | |||||||||||||
Please use __x and __y for consistency with lines 201–207. Quuxplusone: Please use `__x` and `__y` for consistency with lines 201–207.
Separately, don't you need a… | |||||||||||||
template <size_t _Ip, class _Hp, bool> | template <size_t _Ip, class _Hp, bool> | ||||||||||||
class __tuple_leaf | class __tuple_leaf | ||||||||||||
{ | { | ||||||||||||
_Hp __value_; | _Hp __value_; | ||||||||||||
template <class _Tp> | template <class _Tp> | ||||||||||||
static constexpr bool __can_bind_reference() { | static constexpr bool __can_bind_reference() { | ||||||||||||
#if __has_keyword(__reference_binds_to_temporary) | #if __has_keyword(__reference_binds_to_temporary) | ||||||||||||
▲ Show 20 Lines • Show All 72 Lines • ▼ Show 20 Lines | public: | ||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 | _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 | ||||||||||||
int swap(__tuple_leaf& __t) _NOEXCEPT_(__is_nothrow_swappable<__tuple_leaf>::value) | int swap(__tuple_leaf& __t) _NOEXCEPT_(__is_nothrow_swappable<__tuple_leaf>::value) | ||||||||||||
{ | { | ||||||||||||
_VSTD::swap(*this, __t); | _VSTD::swap(*this, __t); | ||||||||||||
return 0; | return 0; | ||||||||||||
} | } | ||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 | |||||||||||||
Same, I would *not* guard this with _LIBCPP_STD_VER since this is internal. ldionne: Same, I would *not* guard this with `_LIBCPP_STD_VER` since this is internal. | |||||||||||||
int swap(const __tuple_leaf& __t) const _NOEXCEPT_(__is_nothrow_swappable<const __tuple_leaf>::value) { | |||||||||||||
_VSTD::swap(*this, __t); | |||||||||||||
No action required, but it looks slightly odd that this uses noexcept directly whereas the one above uses _NOEXCEPT_ . Ditto for the new swap implemented below. jloser: No action required, but it looks slightly odd that this uses `noexcept` directly whereas the… | |||||||||||||
I suspect this (and lines 381 and 479) should be noexcept(is_nothrow_swappable_v<const __tuple_leaf&>). If I'm right, you should be able to (and thus, should) write a regression test that detects this mistake by smacking into the noexcept and std::terminate'ing when in fact it shouldn't. Quuxplusone: I suspect this (and lines 381 and 479) should be `noexcept(is_nothrow_swappable_v<const… | |||||||||||||
Even though I said is_nothrow_swappable_v<const __tuple_leaf&> above, it strikes me now that for consistency with line 300 we really want is_nothrow_swappable_v<const __tuple_leaf> (no ampersand). Quuxplusone: Even though I said `is_nothrow_swappable_v<const __tuple_leaf&>` above, it strikes me now that… | |||||||||||||
return 0; | |||||||||||||
} | |||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 _Hp& get() _NOEXCEPT {return __value_;} | _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 _Hp& get() _NOEXCEPT {return __value_;} | ||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 const _Hp& get() const _NOEXCEPT {return __value_;} | _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 const _Hp& get() const _NOEXCEPT {return __value_;} | ||||||||||||
}; | }; | ||||||||||||
template <size_t _Ip, class _Hp> | template <size_t _Ip, class _Hp> | ||||||||||||
class __tuple_leaf<_Ip, _Hp, true> | class __tuple_leaf<_Ip, _Hp, true> | ||||||||||||
: private _Hp | : private _Hp | ||||||||||||
{ | { | ||||||||||||
▲ Show 20 Lines • Show All 50 Lines • ▼ Show 20 Lines | public: | ||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 | _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 | ||||||||||||
int | int | ||||||||||||
swap(__tuple_leaf& __t) _NOEXCEPT_(__is_nothrow_swappable<__tuple_leaf>::value) | swap(__tuple_leaf& __t) _NOEXCEPT_(__is_nothrow_swappable<__tuple_leaf>::value) | ||||||||||||
{ | { | ||||||||||||
_VSTD::swap(*this, __t); | _VSTD::swap(*this, __t); | ||||||||||||
return 0; | return 0; | ||||||||||||
} | } | ||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 | |||||||||||||
int swap(const __tuple_leaf& __rhs) const _NOEXCEPT_(__is_nothrow_swappable<const __tuple_leaf>::value) { | |||||||||||||
_VSTD::swap(*this, __rhs); | |||||||||||||
return 0; | |||||||||||||
} | |||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 _Hp& get() _NOEXCEPT {return static_cast<_Hp&>(*this);} | _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 _Hp& get() _NOEXCEPT {return static_cast<_Hp&>(*this);} | ||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 const _Hp& get() const _NOEXCEPT {return static_cast<const _Hp&>(*this);} | _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 const _Hp& get() const _NOEXCEPT {return static_cast<const _Hp&>(*this);} | ||||||||||||
}; | }; | ||||||||||||
template <class ..._Tp> | template <class ..._Tp> | ||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 | _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 | ||||||||||||
void __swallow(_Tp&&...) _NOEXCEPT {} | void __swallow(_Tp&&...) _NOEXCEPT {} | ||||||||||||
▲ Show 20 Lines • Show All 74 Lines • ▼ Show 20 Lines | struct _LIBCPP_DECLSPEC_EMPTY_BASES __tuple_impl<__tuple_indices<_Indx...>, _Tp...> | ||||||||||||
__tuple_impl(__tuple_impl&&) = default; | __tuple_impl(__tuple_impl&&) = default; | ||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 | _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 | ||||||||||||
void swap(__tuple_impl& __t) | void swap(__tuple_impl& __t) | ||||||||||||
_NOEXCEPT_(__all<__is_nothrow_swappable<_Tp>::value...>::value) | _NOEXCEPT_(__all<__is_nothrow_swappable<_Tp>::value...>::value) | ||||||||||||
{ | { | ||||||||||||
_VSTD::__swallow(__tuple_leaf<_Indx, _Tp>::swap(static_cast<__tuple_leaf<_Indx, _Tp>&>(__t))...); | _VSTD::__swallow(__tuple_leaf<_Indx, _Tp>::swap(static_cast<__tuple_leaf<_Indx, _Tp>&>(__t))...); | ||||||||||||
} | } | ||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 | |||||||||||||
void swap(const __tuple_impl& __t) const | |||||||||||||
_NOEXCEPT_(__all<__is_nothrow_swappable<const _Tp>::value...>::value) | |||||||||||||
Since this is C++17-or-later, you can use noexcept((is_nothrow_swappable_v<const _Tp&> && ...)) Quuxplusone: Since this is C++17-or-later, you can use `noexcept((is_nothrow_swappable_v<const _Tp&> && ... | |||||||||||||
{ | |||||||||||||
Is there a reason for not doing it like we do for non-const types? Let's use the same approach for consistency, whatever that is. ldionne: Is there a reason for not doing it like we do for non-const types? Let's use the same approach… | |||||||||||||
_VSTD::__swallow(__tuple_leaf<_Indx, _Tp>::swap(static_cast<const __tuple_leaf<_Indx, _Tp>&>(__t))...); | |||||||||||||
} | |||||||||||||
}; | }; | ||||||||||||
template<class _Dest, class _Source, size_t ..._Np> | template<class _Dest, class _Source, size_t ..._Np> | ||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 | _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 | ||||||||||||
void __memberwise_copy_assign(_Dest& __dest, _Source const& __source, __tuple_indices<_Np...>) { | void __memberwise_copy_assign(_Dest& __dest, _Source const& __source, __tuple_indices<_Np...>) { | ||||||||||||
_VSTD::__swallow(((_VSTD::get<_Np>(__dest) = _VSTD::get<_Np>(__source)), void(), 0)...); | _VSTD::__swallow(((_VSTD::get<_Np>(__dest) = _VSTD::get<_Np>(__source)), void(), 0)...); | ||||||||||||
} | } | ||||||||||||
▲ Show 20 Lines • Show All 219 Lines • ▼ Show 20 Lines | public: | ||||||||||||
// Copy and move constructors (including the allocator_arg_t variants) | // Copy and move constructors (including the allocator_arg_t variants) | ||||||||||||
tuple(const tuple&) = default; | tuple(const tuple&) = default; | ||||||||||||
tuple(tuple&&) = default; | tuple(tuple&&) = default; | ||||||||||||
template <class _Alloc, template<class...> class _And = _And, __enable_if_t< | template <class _Alloc, template<class...> class _And = _And, __enable_if_t< | ||||||||||||
_And<is_copy_constructible<_Tp>...>::value | _And<is_copy_constructible<_Tp>...>::value | ||||||||||||
, int> = 0> | , int> = 0> | ||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17 | |||||||||||||
tuple(allocator_arg_t, const _Alloc& __alloc, const tuple& __t) | tuple(allocator_arg_t, const _Alloc& __alloc, const tuple& __t) | ||||||||||||
: __base_(allocator_arg_t(), __alloc, __t) | : __base_(allocator_arg_t(), __alloc, __t) | ||||||||||||
{ } | { } | ||||||||||||
template <class _Alloc, template<class...> class _And = _And, __enable_if_t< | template <class _Alloc, template<class...> class _And = _And, __enable_if_t< | ||||||||||||
_And<is_move_constructible<_Tp>...>::value | _And<is_move_constructible<_Tp>...>::value | ||||||||||||
, int> = 0> | , int> = 0> | ||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17 | |||||||||||||
tuple(allocator_arg_t, const _Alloc& __alloc, tuple&& __t) | tuple(allocator_arg_t, const _Alloc& __alloc, tuple&& __t) | ||||||||||||
: __base_(allocator_arg_t(), __alloc, _VSTD::move(__t)) | : __base_(allocator_arg_t(), __alloc, _VSTD::move(__t)) | ||||||||||||
{ } | { } | ||||||||||||
// tuple(const tuple<U...>&) constructors (including allocator_arg_t variants) | // tuple(const tuple<U...>&) constructors (including allocator_arg_t variants) | ||||||||||||
This comment is now incorrect, I would say instead tuple([const] tuple<U...>&) constructors (including allocator_arg_t variants) or something like that. The && comment below is also incorrect now -- please audit to make sure there aren't others too. ldionne: This comment is now incorrect, I would say instead `tuple([const] tuple<U...>&) constructors… | |||||||||||||
template <class ..._Up> | |||||||||||||
struct _EnableCopyFromOtherTuple : _And< | template <class _OtherTuple, class _DecayedOtherTuple = __uncvref_t<_OtherTuple>, class = void> | ||||||||||||
_Not<is_same<tuple<_Tp...>, tuple<_Up...> > >, | struct _EnableCtorFromUTypesTuple : false_type {}; | ||||||||||||
Really small nitpick, but we generally use ctor instead of ctr for constructor (at least it's what I've seen so far). ldionne: Really small nitpick, but we generally use `ctor` instead of `ctr` for constructor (at least… | |||||||||||||
_Lazy<_Or, | |||||||||||||
_BoolConstant<sizeof...(_Tp) != 1>, | template <class _OtherTuple, class... _Up> | ||||||||||||
struct _EnableCtorFromUTypesTuple<_OtherTuple, tuple<_Up...>, | |||||||||||||
// the length of the packs needs to checked first otherwise the 2 packs cannot be expanded simultaneously below | |||||||||||||
__enable_if_t<sizeof...(_Up) == sizeof...(_Tp)>> : _And< | |||||||||||||
ldionne: | |||||||||||||
// the two conditions below are not in spec. The purpose is to disable the UTypes Ctor when copy/move Ctor can work. | |||||||||||||
// Otherwise, is_constructible can trigger hard error in those cases https://godbolt.org/z/M94cGdKcE | |||||||||||||
What happens if you remove these conditions? The comments should explain what is the behavior that we are preserving. ldionne: What happens if you remove these conditions? The comments should explain what is the behavior… | |||||||||||||
_Not<is_same<_OtherTuple, const tuple&> >, | |||||||||||||
_Not<is_same<_OtherTuple, tuple&&> >, | |||||||||||||
ldionne: | |||||||||||||
is_constructible<_Tp, __copy_cvref_t<_OtherTuple, _Up> >..., | |||||||||||||
_Lazy<_Or, _BoolConstant<sizeof...(_Tp) != 1>, | |||||||||||||
// _Tp and _Up are 1-element packs - the pack expansions look | // _Tp and _Up are 1-element packs - the pack expansions look | ||||||||||||
// weird to avoid tripping up the type traits in degenerate cases | // weird to avoid tripping up the type traits in degenerate cases | ||||||||||||
_Lazy<_And, | _Lazy<_And, | ||||||||||||
_Not<is_convertible<const tuple<_Up>&, _Tp> >..., | _Not<is_same<_Tp, _Up> >..., | ||||||||||||
_Not<is_constructible<_Tp, const tuple<_Up>&> >... | _Not<is_convertible<_OtherTuple, _Tp> >..., | ||||||||||||
_Not<is_constructible<_Tp, _OtherTuple> >... | |||||||||||||
What's __maybe_const? Should it be in this patch? EricWF: What's `__maybe_const`? Should it be in this patch? | |||||||||||||
Ignore this. EricWF: Ignore this. | |||||||||||||
Actually, nevermind. __maybe_const is only available after C++17, but this compiles in older dialects. So we need to expose __maybe_const in older dialects. EricWF: Actually, nevermind. `__maybe_const` is only available after C++17, but this compiles in older… | |||||||||||||
I don't like making these sorts of comments, but I don't understand why the indentation was changed. It used to be lined up to tabs. ldionne: I don't like making these sorts of comments, but I don't understand why the indentation was… | |||||||||||||
I'm not requesting it, however if you'd like to add a regression test where for example _BoolConstant<sizeof...(_Tp) != 1> is false, but evaluating is_constructible<_Tp, _OtherTuple> is a hard error, I think it would catch this. ldionne: I'm not requesting it, however if you'd like to add a regression test where for example… | |||||||||||||
> | |||||||||||||
Mentioning _Not<T> does not instantiate T immediately, it only does when you do _Not<T>::whatever. Since you only do that lazily with my suggested edits to _And and _Or, you don't need _Lazy at this level anymore -- at least I think you don't, but our tests should tell you if I'm right. ldionne: Mentioning `_Not<T>` does not instantiate `T` immediately, it only does when you do `_Not<T>… | |||||||||||||
> | > | ||||||||||||
>, | |||||||||||||
is_constructible<_Tp, const _Up&>... | |||||||||||||
> { }; | > {}; | ||||||||||||
template <class ..._Up, __enable_if_t< | template <class ..._Up, __enable_if_t< | ||||||||||||
_And< | _And< | ||||||||||||
_BoolConstant<sizeof...(_Up) == sizeof...(_Tp)>, | _EnableCtorFromUTypesTuple<const tuple<_Up...>&>, | ||||||||||||
_EnableCopyFromOtherTuple<_Up...>, | |||||||||||||
is_convertible<const _Up&, _Tp>... // explicit check | is_convertible<const _Up&, _Tp>... // explicit check | ||||||||||||
>::value | >::value | ||||||||||||
(1) Can't use requires because of _LIBCPP_HAS_NO_CONCEPTS. Quuxplusone: (1) Can't use `requires` because of `_LIBCPP_HAS_NO_CONCEPTS`.
(2) Don't want to use `requires`… | |||||||||||||
, int> = 0> | , int> = 0> | ||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 | _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 | ||||||||||||
tuple(const tuple<_Up...>& __t) | tuple(const tuple<_Up...>& __t) | ||||||||||||
_NOEXCEPT_((_And<is_nothrow_constructible<_Tp, const _Up&>...>::value)) | _NOEXCEPT_((_And<is_nothrow_constructible<_Tp, const _Up&>...>::value)) | ||||||||||||
: __base_(__t) | : __base_(__t) | ||||||||||||
{ } | { } | ||||||||||||
template <class ..._Up, __enable_if_t< | template <class ..._Up, __enable_if_t< | ||||||||||||
_And< | _And< | ||||||||||||
Right? (Please add a regression test.) Quuxplusone: Right? (Please add a regression test.) | |||||||||||||
_BoolConstant<sizeof...(_Up) == sizeof...(_Tp)>, | _EnableCtorFromUTypesTuple<const tuple<_Up...>&>, | ||||||||||||
_EnableCopyFromOtherTuple<_Up...>, | |||||||||||||
_Not<_Lazy<_And, is_convertible<const _Up&, _Tp>...> > // explicit check | _Not<_Lazy<_And, is_convertible<const _Up&, _Tp>...> > // explicit check | ||||||||||||
>::value | >::value | ||||||||||||
Bikeshed: should explicit(...) be indented on lines 751 and 759? Personally I think not. It's not a "subordinate" clause in any sense; we don't normally indent the return type or constexpr when that's on a line by itself. Quuxplusone: Bikeshed: should `explicit(...)` be indented on lines 751 and 759? Personally I think not. It's… | |||||||||||||
, int> = 0> | , int> = 0> | ||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 | _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 | ||||||||||||
explicit tuple(const tuple<_Up...>& __t) | explicit tuple(const tuple<_Up...>& __t) | ||||||||||||
_NOEXCEPT_((_And<is_nothrow_constructible<_Tp, const _Up&>...>::value)) | _NOEXCEPT_((_And<is_nothrow_constructible<_Tp, const _Up&>...>::value)) | ||||||||||||
: __base_(__t) | : __base_(__t) | ||||||||||||
{ } | { } | ||||||||||||
template <class ..._Up, class _Alloc, __enable_if_t< | template <class ..._Up, class _Alloc, __enable_if_t< | ||||||||||||
_And< | _And< | ||||||||||||
_BoolConstant<sizeof...(_Up) == sizeof...(_Tp)>, | _EnableCtorFromUTypesTuple<const tuple<_Up...>&>, | ||||||||||||
_EnableCopyFromOtherTuple<_Up...>, | |||||||||||||
is_convertible<const _Up&, _Tp>... // explicit check | is_convertible<const _Up&, _Tp>... // explicit check | ||||||||||||
>::value | >::value | ||||||||||||
, int> = 0> | , int> = 0> | ||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17 | _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17 | ||||||||||||
tuple(allocator_arg_t, const _Alloc& __a, const tuple<_Up...>& __t) | tuple(allocator_arg_t, const _Alloc& __a, const tuple<_Up...>& __t) | ||||||||||||
: __base_(allocator_arg_t(), __a, __t) | : __base_(allocator_arg_t(), __a, __t) | ||||||||||||
{ } | { } | ||||||||||||
template <class ..._Up, class _Alloc, __enable_if_t< | template <class ..._Up, class _Alloc, __enable_if_t< | ||||||||||||
_And< | _And< | ||||||||||||
_BoolConstant<sizeof...(_Up) == sizeof...(_Tp)>, | _EnableCtorFromUTypesTuple<const tuple<_Up...>&>, | ||||||||||||
_EnableCopyFromOtherTuple<_Up...>, | |||||||||||||
_Not<_Lazy<_And, is_convertible<const _Up&, _Tp>...> > // explicit check | _Not<_Lazy<_And, is_convertible<const _Up&, _Tp>...> > // explicit check | ||||||||||||
>::value | >::value | ||||||||||||
, int> = 0> | , int> = 0> | ||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17 | _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17 | ||||||||||||
explicit tuple(allocator_arg_t, const _Alloc& __a, const tuple<_Up...>& __t) | explicit tuple(allocator_arg_t, const _Alloc& __a, const tuple<_Up...>& __t) | ||||||||||||
: __base_(allocator_arg_t(), __a, __t) | : __base_(allocator_arg_t(), __a, __t) | ||||||||||||
{ } | { } | ||||||||||||
#if _LIBCPP_STD_VER > 20 | |||||||||||||
// tuple(tuple<U...>&) constructors (including allocator_arg_t variants) | |||||||||||||
template <class... _Up, enable_if_t< | |||||||||||||
_EnableCtorFromUTypesTuple<tuple<_Up...>&>::value>* = nullptr> | |||||||||||||
_LIBCPP_HIDE_FROM_ABI constexpr | |||||||||||||
explicit(!(is_convertible_v<_Up&, _Tp> && ...)) | |||||||||||||
tuple(tuple<_Up...>& __t) : __base_(__t) {} | |||||||||||||
template <class _Alloc, class... _Up, enable_if_t< | |||||||||||||
_EnableCtorFromUTypesTuple<tuple<_Up...>&>::value>* = nullptr> | |||||||||||||
_LIBCPP_HIDE_FROM_ABI constexpr | |||||||||||||
explicit(!(is_convertible_v<_Up&, _Tp> && ...)) | |||||||||||||
tuple(allocator_arg_t, const _Alloc& __alloc, tuple<_Up...>& __t) : __base_(allocator_arg_t(), __alloc, __t) {} | |||||||||||||
#endif // _LIBCPP_STD_VER > 20 | |||||||||||||
// tuple(tuple<U...>&&) constructors (including allocator_arg_t variants) | // tuple(tuple<U...>&&) constructors (including allocator_arg_t variants) | ||||||||||||
You're going to need the same bool _Const technique here. Please add a regression test that can detect the problem (e.g. via a type A where A&& is convertible to B but B(const A&&)=delete, and vice versa). Quuxplusone: You're going to need the same `bool _Const` technique here. Please add a regression test that… | |||||||||||||
I wasn't able to reproduce it. philnik: I wasn't able to reproduce it. | |||||||||||||
template <class ..._Up> | |||||||||||||
struct _EnableMoveFromOtherTuple : _And< | |||||||||||||
_Not<is_same<tuple<_Tp...>, tuple<_Up...> > >, | |||||||||||||
_Lazy<_Or, | |||||||||||||
_BoolConstant<sizeof...(_Tp) != 1>, | |||||||||||||
// _Tp and _Up are 1-element packs - the pack expansions look | |||||||||||||
// weird to avoid tripping up the type traits in degenerate cases | |||||||||||||
_Lazy<_And, | |||||||||||||
_Not<is_convertible<tuple<_Up>, _Tp> >..., | |||||||||||||
_Not<is_constructible<_Tp, tuple<_Up> > >... | |||||||||||||
> | |||||||||||||
>, | |||||||||||||
is_constructible<_Tp, _Up>... | |||||||||||||
> { }; | |||||||||||||
Don't you need to make changes here too to pass _Const and use __maybe_const<_Const, _Tp>&& instead? I think this should look like: template <bool _Const, class ..._Up> struct _EnableMoveFromOtherTuple : _And< _Not<is_same<tuple<_Tp...>, tuple<_Up...> > >, _Lazy<_Or, _BoolConstant<sizeof...(_Tp) != 1>, // _Tp and _Up are 1-element packs - the pack expansions look // weird to avoid tripping up the type traits in degenerate cases _Lazy<_And, _Not<is_convertible<__maybe_const<_Const, tuple<_Up>>&&, _Tp> >..., _Not<is_constructible<_Tp, __maybe_const<_Const, tuple<_Up>>&& > >... > >, is_constructible<_Tp, __maybe_const<_Const, _Up>&&>... > { }; This should be observable if you have a type Foo in your tuple like this: struct Foo { Foo(Other const&&); Foo(Other&&) = delete; }; If you use is_constructible<_Tp, _Up> (where _Tp=Foo and _Up=Other), we'll prefer the Foo(Other&&) constructor and the answer will be "no". If we use my proposed version, it should use Foo(Other const&&) instead and be satisfied. https://godbolt.org/z/b47ffv43P Please add a test for this in all the constructors that take rvalue-refs. ldionne: Don't you need to make changes here too to pass `_Const` and use `__maybe_const<_Const, _Tp>&&`… | |||||||||||||
template <class ..._Up, __enable_if_t< | template <class ..._Up, __enable_if_t< | ||||||||||||
_And< | _And< | ||||||||||||
_BoolConstant<sizeof...(_Up) == sizeof...(_Tp)>, | _EnableCtorFromUTypesTuple<tuple<_Up...>&&>, | ||||||||||||
_EnableMoveFromOtherTuple<_Up...>, | |||||||||||||
is_convertible<_Up, _Tp>... // explicit check | is_convertible<_Up, _Tp>... // explicit check | ||||||||||||
>::value | >::value | ||||||||||||
, int> = 0> | , int> = 0> | ||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 | _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 | ||||||||||||
& should presumably be &&; please add a regression test. Quuxplusone: `&` should presumably be `&&`; please add a regression test. | |||||||||||||
tuple(tuple<_Up...>&& __t) | tuple(tuple<_Up...>&& __t) | ||||||||||||
_NOEXCEPT_((_And<is_nothrow_constructible<_Tp, _Up>...>::value)) | _NOEXCEPT_((_And<is_nothrow_constructible<_Tp, _Up>...>::value)) | ||||||||||||
: __base_(_VSTD::move(__t)) | : __base_(_VSTD::move(__t)) | ||||||||||||
{ } | { } | ||||||||||||
template <class ..._Up, __enable_if_t< | template <class ..._Up, __enable_if_t< | ||||||||||||
_And< | _And< | ||||||||||||
_BoolConstant<sizeof...(_Up) == sizeof...(_Tp)>, | _EnableCtorFromUTypesTuple<tuple<_Up...>&&>, | ||||||||||||
_EnableMoveFromOtherTuple<_Up...>, | |||||||||||||
_Not<_Lazy<_And, is_convertible<_Up, _Tp>...> > // explicit check | _Not<_Lazy<_And, is_convertible<_Up, _Tp>...> > // explicit check | ||||||||||||
>::value | >::value | ||||||||||||
, int> = 0> | , int> = 0> | ||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 | _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 | ||||||||||||
explicit tuple(tuple<_Up...>&& __t) | explicit tuple(tuple<_Up...>&& __t) | ||||||||||||
_NOEXCEPT_((_And<is_nothrow_constructible<_Tp, _Up>...>::value)) | _NOEXCEPT_((_And<is_nothrow_constructible<_Tp, _Up>...>::value)) | ||||||||||||
: __base_(_VSTD::move(__t)) | : __base_(_VSTD::move(__t)) | ||||||||||||
{ } | { } | ||||||||||||
template <class _Alloc, class ..._Up, __enable_if_t< | template <class _Alloc, class ..._Up, __enable_if_t< | ||||||||||||
_And< | _And< | ||||||||||||
_BoolConstant<sizeof...(_Up) == sizeof...(_Tp)>, | _EnableCtorFromUTypesTuple<tuple<_Up...>&&>, | ||||||||||||
_EnableMoveFromOtherTuple<_Up...>, | |||||||||||||
is_convertible<_Up, _Tp>... // explicit check | is_convertible<_Up, _Tp>... // explicit check | ||||||||||||
>::value | >::value | ||||||||||||
, int> = 0> | , int> = 0> | ||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17 | _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17 | ||||||||||||
tuple(allocator_arg_t, const _Alloc& __a, tuple<_Up...>&& __t) | tuple(allocator_arg_t, const _Alloc& __a, tuple<_Up...>&& __t) | ||||||||||||
: __base_(allocator_arg_t(), __a, _VSTD::move(__t)) | : __base_(allocator_arg_t(), __a, _VSTD::move(__t)) | ||||||||||||
{ } | { } | ||||||||||||
template <class _Alloc, class ..._Up, __enable_if_t< | template <class _Alloc, class ..._Up, __enable_if_t< | ||||||||||||
_And< | _And< | ||||||||||||
_BoolConstant<sizeof...(_Up) == sizeof...(_Tp)>, | _EnableCtorFromUTypesTuple<tuple<_Up...>&&>, | ||||||||||||
_EnableMoveFromOtherTuple<_Up...>, | |||||||||||||
_Not<_Lazy<_And, is_convertible<_Up, _Tp>...> > // explicit check | _Not<_Lazy<_And, is_convertible<_Up, _Tp>...> > // explicit check | ||||||||||||
>::value | >::value | ||||||||||||
, int> = 0> | , int> = 0> | ||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17 | _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17 | ||||||||||||
explicit tuple(allocator_arg_t, const _Alloc& __a, tuple<_Up...>&& __t) | explicit tuple(allocator_arg_t, const _Alloc& __a, tuple<_Up...>&& __t) | ||||||||||||
: __base_(allocator_arg_t(), __a, _VSTD::move(__t)) | : __base_(allocator_arg_t(), __a, _VSTD::move(__t)) | ||||||||||||
{ } | { } | ||||||||||||
#if _LIBCPP_STD_VER > 20 | |||||||||||||
// tuple(const tuple<U...>&&) constructors (including allocator_arg_t variants) | |||||||||||||
template <class... _Up, enable_if_t< | |||||||||||||
_EnableCtorFromUTypesTuple<const tuple<_Up...>&&>::value>* = nullptr> | |||||||||||||
_LIBCPP_HIDE_FROM_ABI constexpr | |||||||||||||
explicit(!(is_convertible_v<const _Up&&, _Tp> && ...)) | |||||||||||||
tuple(const tuple<_Up...>&& __t) : __base_(std::move(__t)) {} | |||||||||||||
template <class _Alloc, class... _Up, enable_if_t< | |||||||||||||
_EnableCtorFromUTypesTuple<const tuple<_Up...>&&>::value>* = nullptr> | |||||||||||||
_LIBCPP_HIDE_FROM_ABI constexpr | |||||||||||||
explicit(!(is_convertible_v<const _Up&&, _Tp> && ...)) | |||||||||||||
tuple(allocator_arg_t, const _Alloc& __alloc, const tuple<_Up...>&& __t) | |||||||||||||
: __base_(allocator_arg_t(), __alloc, std::move(__t)) {} | |||||||||||||
#endif // _LIBCPP_STD_VER > 20 | |||||||||||||
// tuple(const pair<U1, U2>&) constructors (including allocator_arg_t variants) | // tuple(const pair<U1, U2>&) constructors (including allocator_arg_t variants) | ||||||||||||
template <class _Up1, class _Up2, class ..._DependentTp> | |||||||||||||
struct _EnableImplicitCopyFromPair : _And< | template <template <class...> class Pred, class _Pair, class _DecayedPair = __uncvref_t<_Pair>, class _Tuple = tuple> | ||||||||||||
is_constructible<_FirstType<_DependentTp...>, const _Up1&>, | struct _CtorPredicateFromPair : false_type{}; | ||||||||||||
These bits of SFINAE were written very specifically because tuple is a very weird beast. I'm surprised we don't have a test in the test suite that breaks after changing this, because I thought I added one. There are certain SFINAE loops that crop up where checking these properties in the specific order is/was important. EricWF: These bits of SFINAE were written very specifically because tuple is a very weird beast.
I'm… | |||||||||||||
I think what Eric's saying here is that you need to test for _EnableCopyFromPair *before* you test the two is_convertible conditions. I think you could witness the difference by using a type that is *not* constructible from _Up, but where a conversion from _Up triggers a hard error. In that case, the fact that you changed the order of checking will cause you to instantiate the conversion operator before the constructor, and explode. If you had checked for the constructibility first, you'd have gotten a false answer and stopped there, instead of being tripped up by the hard error later on while trying to check whether the constructor should be explicit or not. Please add tests for this -- we actually see these sorts of funky bad-behaved constructors and conversions in the wild. I remember breaking nlohmann::json with a change a couple years ago -- it wasn't fun. I'm not sure whether it ended up being our fault our theirs, but regardless, we want to be bullet proof to avoid breaking widely used libraries, because we look like jokers when that happens :-). ldionne: I think what Eric's saying here is that you need to test for `_EnableCopyFromPair` *before* you… | |||||||||||||
I fixed the ordering issue from the previous version. But I am not sure If I understand the test case. IIUC, you are suggesting a case where IIUC, convertible is a stricter version of constructible. if is_convertible_v has a hard error, the conversion operator/constructor that causes the error would also cause a hard error when evaluating is_constructible. or did I miss something? huixie90: I fixed the ordering issue from the previous version. But I am not sure If I understand the… | |||||||||||||
I think I was thinking about a type having a conversion operator that triggers a hard error. However, it doesn't seem to apply anymore, but it would be nice to ensure that we don't trip over a type T where is_constructible<T, U>::value is false, but where is_convertible<T, U>::value is a hard error (by defining a conversion operator from U to T that causes a hard error). Let me know if you think this doesn't make sense, it might be impossible to actually trigger this issue but I'd need to dive deeper to be certain. ldionne: I think I was thinking about a type having a conversion operator that triggers a hard error. | |||||||||||||
hmm. IIUC, is_constructible also looks for conversion operator. If a conversion operator from U to T causes a hard error, both is_convertible and is_constructible would cause hard error. or, did I miss anything? huixie90: hmm. IIUC, `is_constructible` also looks for conversion operator. If a conversion operator from… | |||||||||||||
I think you're right. ldionne: I think you're right. | |||||||||||||
is_constructible<_SecondType<_DependentTp...>, const _Up2&>, | |||||||||||||
is_convertible<const _Up1&, _FirstType<_DependentTp...> >, // explicit check | template <template <class...> class Pred, class _Pair, class _Up1, class _Up2, class _Tp1, class _Tp2> | ||||||||||||
is_convertible<const _Up2&, _SecondType<_DependentTp...> > | struct _CtorPredicateFromPair<Pred, _Pair, pair<_Up1, _Up2>, tuple<_Tp1, _Tp2> > : _And< | ||||||||||||
Pred<_Tp1, __copy_cvref_t<_Pair, _Up1> >, | |||||||||||||
Pred<_Tp2, __copy_cvref_t<_Pair, _Up2> > | |||||||||||||
> { }; | > {}; | ||||||||||||
template <class _Up1, class _Up2, class ..._DependentTp> | template <class _Pair> | ||||||||||||
struct _EnableExplicitCopyFromPair : _And< | struct _EnableCtorFromPair : _CtorPredicateFromPair<is_constructible, _Pair>{}; | ||||||||||||
is_constructible<_FirstType<_DependentTp...>, const _Up1&>, | |||||||||||||
is_constructible<_SecondType<_DependentTp...>, const _Up2&>, | template <class _Pair> | ||||||||||||
_Not<is_convertible<const _Up1&, _FirstType<_DependentTp...> > >, // explicit check | struct _NothrowConstructibleFromPair : _CtorPredicateFromPair<is_nothrow_constructible, _Pair>{}; | ||||||||||||
_Not<is_convertible<const _Up2&, _SecondType<_DependentTp...> > > | |||||||||||||
Do we need _SecondType and _FirstType in <type_traits> anymore? If not, let's remove them. ldionne: Do we need `_SecondType` and `_FirstType` in `<type_traits>` anymore? If not, let's remove them. | |||||||||||||
They were used in the noexcept specifier of some constructors and assignment operators. I went ahead refactoring these functions and _FirstType and _SecondType are no longer used huixie90: They were used in the `noexcept` specifier of some constructors and assignment operators. I… | |||||||||||||
template <class _Pair, class _DecayedPair = __uncvref_t<_Pair>, class _Tuple = tuple> | |||||||||||||
struct _BothImplicitlyConvertible : false_type{}; | |||||||||||||
template <class _Pair, class _Up1, class _Up2, class _Tp1, class _Tp2> | |||||||||||||
struct _BothImplicitlyConvertible<_Pair, pair<_Up1, _Up2>, tuple<_Tp1, _Tp2> > : _And< | |||||||||||||
is_convertible<__copy_cvref_t<_Pair, _Up1>, _Tp1>, | |||||||||||||
is_convertible<__copy_cvref_t<_Pair, _Up2>, _Tp2> | |||||||||||||
> { }; | > {}; | ||||||||||||
template <class _Up1, class _Up2, template<class...> class _And = _And, __enable_if_t< | template <class _Up1, class _Up2, template<class...> class _And = _And, __enable_if_t< | ||||||||||||
_And< | _And< | ||||||||||||
_BoolConstant<sizeof...(_Tp) == 2>, | _EnableCtorFromPair<const pair<_Up1, _Up2>&>, | ||||||||||||
_EnableImplicitCopyFromPair<_Up1, _Up2, _Tp...> | _BothImplicitlyConvertible<const pair<_Up1, _Up2>&> // explicit check | ||||||||||||
It's pretty obvious, but just to stay consistent with the other constructors above. This also applies to the other constructors below. ldionne: It's pretty obvious, but just to stay consistent with the other constructors above. This also… | |||||||||||||
>::value | >::value | ||||||||||||
, int> = 0> | , int> = 0> | ||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 | _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 | ||||||||||||
tuple(const pair<_Up1, _Up2>& __p) | tuple(const pair<_Up1, _Up2>& __p) | ||||||||||||
_NOEXCEPT_((_And< | _NOEXCEPT_((_NothrowConstructibleFromPair<const pair<_Up1, _Up2>&>::value)) | ||||||||||||
is_nothrow_constructible<_FirstType<_Tp...>, const _Up1&>, | |||||||||||||
is_nothrow_constructible<_SecondType<_Tp...>, const _Up2&> | |||||||||||||
>::value)) | |||||||||||||
: __base_(__p) | : __base_(__p) | ||||||||||||
{ } | { } | ||||||||||||
template <class _Up1, class _Up2, template<class...> class _And = _And, __enable_if_t< | template <class _Up1, class _Up2, template<class...> class _And = _And, __enable_if_t< | ||||||||||||
_And< | _And< | ||||||||||||
_BoolConstant<sizeof...(_Tp) == 2>, | _EnableCtorFromPair<const pair<_Up1, _Up2>&>, | ||||||||||||
_EnableExplicitCopyFromPair<_Up1, _Up2, _Tp...> | _Not<_BothImplicitlyConvertible<const pair<_Up1, _Up2>&> > // explicit check | ||||||||||||
>::value | >::value | ||||||||||||
, int> = 0> | , int> = 0> | ||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 | _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 | ||||||||||||
explicit tuple(const pair<_Up1, _Up2>& __p) | explicit tuple(const pair<_Up1, _Up2>& __p) | ||||||||||||
_NOEXCEPT_((_And< | _NOEXCEPT_((_NothrowConstructibleFromPair<const pair<_Up1, _Up2>&>::value)) | ||||||||||||
is_nothrow_constructible<_FirstType<_Tp...>, const _Up1&>, | |||||||||||||
is_nothrow_constructible<_SecondType<_Tp...>, const _Up2&> | |||||||||||||
>::value)) | |||||||||||||
: __base_(__p) | : __base_(__p) | ||||||||||||
{ } | { } | ||||||||||||
template <class _Alloc, class _Up1, class _Up2, template<class...> class _And = _And, __enable_if_t< | template <class _Alloc, class _Up1, class _Up2, template<class...> class _And = _And, __enable_if_t< | ||||||||||||
_And< | _And< | ||||||||||||
_BoolConstant<sizeof...(_Tp) == 2>, | _EnableCtorFromPair<const pair<_Up1, _Up2>&>, | ||||||||||||
_EnableImplicitCopyFromPair<_Up1, _Up2, _Tp...> | _BothImplicitlyConvertible<const pair<_Up1, _Up2>&> // explicit check | ||||||||||||
>::value | >::value | ||||||||||||
, int> = 0> | , int> = 0> | ||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17 | _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17 | ||||||||||||
tuple(allocator_arg_t, const _Alloc& __a, const pair<_Up1, _Up2>& __p) | tuple(allocator_arg_t, const _Alloc& __a, const pair<_Up1, _Up2>& __p) | ||||||||||||
: __base_(allocator_arg_t(), __a, __p) | : __base_(allocator_arg_t(), __a, __p) | ||||||||||||
{ } | { } | ||||||||||||
template <class _Alloc, class _Up1, class _Up2, template<class...> class _And = _And, __enable_if_t< | template <class _Alloc, class _Up1, class _Up2, template<class...> class _And = _And, __enable_if_t< | ||||||||||||
_And< | _And< | ||||||||||||
_BoolConstant<sizeof...(_Tp) == 2>, | _EnableCtorFromPair<const pair<_Up1, _Up2>&>, | ||||||||||||
_EnableExplicitCopyFromPair<_Up1, _Up2, _Tp...> | _Not<_BothImplicitlyConvertible<const pair<_Up1, _Up2>&> > // explicit check | ||||||||||||
>::value | >::value | ||||||||||||
, int> = 0> | , int> = 0> | ||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17 | _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17 | ||||||||||||
explicit tuple(allocator_arg_t, const _Alloc& __a, const pair<_Up1, _Up2>& __p) | explicit tuple(allocator_arg_t, const _Alloc& __a, const pair<_Up1, _Up2>& __p) | ||||||||||||
: __base_(allocator_arg_t(), __a, __p) | : __base_(allocator_arg_t(), __a, __p) | ||||||||||||
{ } | { } | ||||||||||||
// tuple(pair<U1, U2>&&) constructors (including allocator_arg_t variants) | #if _LIBCPP_STD_VER > 20 | ||||||||||||
template <class _Up1, class _Up2, class ..._DependentTp> | // tuple(pair<U1, U2>&) constructors (including allocator_arg_t variants) | ||||||||||||
struct _EnableImplicitMoveFromPair : _And< | |||||||||||||
is_constructible<_FirstType<_DependentTp...>, _Up1>, | |||||||||||||
is_constructible<_SecondType<_DependentTp...>, _Up2>, | |||||||||||||
is_convertible<_Up1, _FirstType<_DependentTp...> >, // explicit check | |||||||||||||
is_convertible<_Up2, _SecondType<_DependentTp...> > | |||||||||||||
> { }; | |||||||||||||
template <class _Up1, class _Up2, class ..._DependentTp> | template <class _U1, class _U2, enable_if_t< | ||||||||||||
struct _EnableExplicitMoveFromPair : _And< | _EnableCtorFromPair<pair<_U1, _U2>&>::value>* = nullptr> | ||||||||||||
is_constructible<_FirstType<_DependentTp...>, _Up1>, | _LIBCPP_HIDE_FROM_ABI constexpr | ||||||||||||
is_constructible<_SecondType<_DependentTp...>, _Up2>, | explicit(!_BothImplicitlyConvertible<pair<_U1, _U2>&>::value) | ||||||||||||
_Not<is_convertible<_Up1, _FirstType<_DependentTp...> > >, // explicit check | tuple(pair<_U1, _U2>& __p) : __base_(__p) {} | ||||||||||||
_Not<is_convertible<_Up2, _SecondType<_DependentTp...> > > | |||||||||||||
> { }; | template <class _Alloc, class _U1, class _U2, enable_if_t< | ||||||||||||
_EnableCtorFromPair<std::pair<_U1, _U2>&>::value>* = nullptr> | |||||||||||||
_LIBCPP_HIDE_FROM_ABI constexpr | |||||||||||||
explicit(!_BothImplicitlyConvertible<pair<_U1, _U2>&>::value) | |||||||||||||
tuple(allocator_arg_t, const _Alloc& __alloc, pair<_U1, _U2>& __p) : __base_(allocator_arg_t(), __alloc, __p) {} | |||||||||||||
#endif | |||||||||||||
If we don't have 2 types in the tuple, we shouldn't even be attempting this is_convertible check on the first one. Don't instantiate any constructability/convertability checks in tuple if there are obvious reasons why the constructor evaluating them shouldn't be chosen, such as the arity. EricWF: If we don't have 2 types in the tuple, we shouldn't even be attempting this `is_convertible`… | |||||||||||||
// tuple(pair<U1, U2>&&) constructors (including allocator_arg_t variants) | |||||||||||||
Again I haven't checked the wording (just cppreference), but this feels convoluted and thus wrong. It should be more like:
Which I think is how it was. Actually I think your way of doing it below (lines 987-1012) is fine, except that you're misusing requires again — and you probably need to #if _LIBCPP_STD_VER <= 20 the existing ctors from pair, so that in C++23 we get only the new conditionally-explicit four below. Quuxplusone: Again I haven't checked the wording (just cppreference), but this feels convoluted and thus… | |||||||||||||
template <class _Up1, class _Up2, template<class...> class _And = _And, __enable_if_t< | template <class _Up1, class _Up2, template<class...> class _And = _And, __enable_if_t< | ||||||||||||
_And< | _And< | ||||||||||||
_BoolConstant<sizeof...(_Tp) == 2>, | _EnableCtorFromPair<pair<_Up1, _Up2>&&>, | ||||||||||||
_EnableImplicitMoveFromPair<_Up1, _Up2, _Tp...> | _BothImplicitlyConvertible<pair<_Up1, _Up2>&&> // explicit check | ||||||||||||
>::value | >::value | ||||||||||||
, int> = 0> | , int> = 0> | ||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 | _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 | ||||||||||||
tuple(pair<_Up1, _Up2>&& __p) | tuple(pair<_Up1, _Up2>&& __p) | ||||||||||||
_NOEXCEPT_((_And< | _NOEXCEPT_((_NothrowConstructibleFromPair<pair<_Up1, _Up2>&&>::value)) | ||||||||||||
is_nothrow_constructible<_FirstType<_Tp...>, _Up1>, | |||||||||||||
is_nothrow_constructible<_SecondType<_Tp...>, _Up2> | |||||||||||||
>::value)) | |||||||||||||
: __base_(_VSTD::move(__p)) | : __base_(_VSTD::move(__p)) | ||||||||||||
{ } | { } | ||||||||||||
template <class _Up1, class _Up2, template<class...> class _And = _And, __enable_if_t< | template <class _Up1, class _Up2, template<class...> class _And = _And, __enable_if_t< | ||||||||||||
_And< | _And< | ||||||||||||
_BoolConstant<sizeof...(_Tp) == 2>, | _EnableCtorFromPair<pair<_Up1, _Up2>&&>, | ||||||||||||
Minor stylistic thing, but I'd do class _Alloc, class _U1, Class _U2 on the same line for consistency with the rest of the code in this file. ldionne: Minor stylistic thing, but I'd do `class _Alloc, class _U1, Class _U2` on the same line for… | |||||||||||||
_EnableExplicitMoveFromPair<_Up1, _Up2, _Tp...> | _Not<_BothImplicitlyConvertible<pair<_Up1, _Up2>&&> > // explicit check | ||||||||||||
>::value | >::value | ||||||||||||
, int> = 0> | , int> = 0> | ||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 | _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 | ||||||||||||
explicit tuple(pair<_Up1, _Up2>&& __p) | explicit tuple(pair<_Up1, _Up2>&& __p) | ||||||||||||
_NOEXCEPT_((_And< | _NOEXCEPT_((_NothrowConstructibleFromPair<pair<_Up1, _Up2>&&>::value)) | ||||||||||||
is_nothrow_constructible<_FirstType<_Tp...>, _Up1>, | |||||||||||||
is_nothrow_constructible<_SecondType<_Tp...>, _Up2> | |||||||||||||
>::value)) | |||||||||||||
: __base_(_VSTD::move(__p)) | : __base_(_VSTD::move(__p)) | ||||||||||||
{ } | { } | ||||||||||||
template <class _Alloc, class _Up1, class _Up2, template<class...> class _And = _And, __enable_if_t< | template <class _Alloc, class _Up1, class _Up2, template<class...> class _And = _And, __enable_if_t< | ||||||||||||
_And< | _And< | ||||||||||||
_BoolConstant<sizeof...(_Tp) == 2>, | _EnableCtorFromPair<pair<_Up1, _Up2>&&>, | ||||||||||||
_EnableImplicitMoveFromPair<_Up1, _Up2, _Tp...> | _BothImplicitlyConvertible<pair<_Up1, _Up2>&&> // explicit check | ||||||||||||
>::value | >::value | ||||||||||||
, int> = 0> | , int> = 0> | ||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17 | _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17 | ||||||||||||
tuple(allocator_arg_t, const _Alloc& __a, pair<_Up1, _Up2>&& __p) | tuple(allocator_arg_t, const _Alloc& __a, pair<_Up1, _Up2>&& __p) | ||||||||||||
: __base_(allocator_arg_t(), __a, _VSTD::move(__p)) | : __base_(allocator_arg_t(), __a, _VSTD::move(__p)) | ||||||||||||
{ } | { } | ||||||||||||
template <class _Alloc, class _Up1, class _Up2, template<class...> class _And = _And, __enable_if_t< | template <class _Alloc, class _Up1, class _Up2, template<class...> class _And = _And, __enable_if_t< | ||||||||||||
_And< | _And< | ||||||||||||
_BoolConstant<sizeof...(_Tp) == 2>, | _EnableCtorFromPair<pair<_Up1, _Up2>&&>, | ||||||||||||
_EnableExplicitMoveFromPair<_Up1, _Up2, _Tp...> | _Not<_BothImplicitlyConvertible<pair<_Up1, _Up2>&&> > // explicit check | ||||||||||||
>::value | >::value | ||||||||||||
, int> = 0> | , int> = 0> | ||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17 | _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17 | ||||||||||||
explicit tuple(allocator_arg_t, const _Alloc& __a, pair<_Up1, _Up2>&& __p) | explicit tuple(allocator_arg_t, const _Alloc& __a, pair<_Up1, _Up2>&& __p) | ||||||||||||
: __base_(allocator_arg_t(), __a, _VSTD::move(__p)) | : __base_(allocator_arg_t(), __a, _VSTD::move(__p)) | ||||||||||||
{ } | { } | ||||||||||||
#if _LIBCPP_STD_VER > 20 | |||||||||||||
// tuple(const pair<U1, U2>&&) constructors (including allocator_arg_t variants) | |||||||||||||
template <class _U1, class _U2, enable_if_t< | |||||||||||||
_EnableCtorFromPair<const pair<_U1, _U2>&&>::value>* = nullptr> | |||||||||||||
_LIBCPP_HIDE_FROM_ABI constexpr | |||||||||||||
explicit(!_BothImplicitlyConvertible<const pair<_U1, _U2>&&>::value) | |||||||||||||
tuple(const pair<_U1, _U2>&& __p) : __base_(std::move(__p)) {} | |||||||||||||
template <class _Alloc, class _U1, class _U2, enable_if_t< | |||||||||||||
_EnableCtorFromPair<const pair<_U1, _U2>&&>::value>* = nullptr> | |||||||||||||
_LIBCPP_HIDE_FROM_ABI constexpr | |||||||||||||
explicit(!_BothImplicitlyConvertible<const pair<_U1, _U2>&&>::value) | |||||||||||||
tuple(allocator_arg_t, const _Alloc& __alloc, const pair<_U1, _U2>&& __p) | |||||||||||||
: __base_(allocator_arg_t(), __alloc, std::move(__p)) {} | |||||||||||||
#endif // _LIBCPP_STD_VER > 20 | |||||||||||||
// [tuple.assign] | // [tuple.assign] | ||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17 | _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17 | ||||||||||||
tuple& operator=(_If<_And<is_copy_assignable<_Tp>...>::value, tuple, __nat> const& __tuple) | tuple& operator=(_If<_And<is_copy_assignable<_Tp>...>::value, tuple, __nat> const& __tuple) | ||||||||||||
_NOEXCEPT_((_And<is_nothrow_copy_assignable<_Tp>...>::value)) | _NOEXCEPT_((_And<is_nothrow_copy_assignable<_Tp>...>::value)) | ||||||||||||
{ | { | ||||||||||||
_VSTD::__memberwise_copy_assign(*this, __tuple, | _VSTD::__memberwise_copy_assign(*this, __tuple, | ||||||||||||
typename __make_tuple_indices<sizeof...(_Tp)>::type()); | typename __make_tuple_indices<sizeof...(_Tp)>::type()); | ||||||||||||
return *this; | return *this; | ||||||||||||
} | } | ||||||||||||
#if _LIBCPP_STD_VER > 20 | |||||||||||||
_LIBCPP_HIDE_FROM_ABI constexpr | |||||||||||||
const tuple& operator=(tuple const& __tuple) const | |||||||||||||
For comparison against line 1101, I think it'd be best to use is_assignable<X&, Y> for every one of these conditions, rather than switching from is_assignable to is_copy_assignable/is_move_assignable in special cases. Quuxplusone: For comparison against line 1101, I think it'd be best to use `is_assignable<X&, Y>` for every… | |||||||||||||
We could use requires instead since we are in C++ > 20 and we only support compilers that support concepts. ldionne: We could use `requires` instead since we are in C++ > 20 and we only support compilers that… | |||||||||||||
requires (_And<is_copy_assignable<const _Tp>...>::value) { | |||||||||||||
std::__memberwise_copy_assign(*this, __tuple, typename __make_tuple_indices<sizeof...(_Tp)>::type()); | |||||||||||||
return *this; | |||||||||||||
} | |||||||||||||
_LIBCPP_HIDE_FROM_ABI constexpr | |||||||||||||
I don't know that we need to do the _If trick here. Because it's not the true "copy assignment operator", we don't need to worry about the compiler generating one. Try writing these like normal overloads with normal SFINAE. EricWF: I don't know that we need to do the `_If` trick here. Because it's not the true "copy… | |||||||||||||
For this one, I tempt to keep _If as it is. few reasons:
huixie90: For this one, I tempt to keep `_If` as it is. few reasons:
1. This function does not naturally… | |||||||||||||
Per the above comment, we can use requires here and mark this original comment as done, IMO. ldionne: Per the above comment, we can use `requires` here and mark this original comment as done, IMO. | |||||||||||||
const tuple& operator=(tuple&& __tuple) const | |||||||||||||
requires (_And<is_assignable<const _Tp&, _Tp>...>::value) { | |||||||||||||
std::__memberwise_forward_assign(*this, | |||||||||||||
std::move(__tuple), | |||||||||||||
__tuple_types<_Tp...>(), | |||||||||||||
typename __make_tuple_indices<sizeof...(_Tp)>::type()); | |||||||||||||
return *this; | |||||||||||||
} | |||||||||||||
#endif // _LIBCPP_STD_VER > 20 | |||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17 | _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17 | ||||||||||||
tuple& operator=(_If<_And<is_move_assignable<_Tp>...>::value, tuple, __nat>&& __tuple) | tuple& operator=(_If<_And<is_move_assignable<_Tp>...>::value, tuple, __nat>&& __tuple) | ||||||||||||
Are we implementing this for std::array too? EricWF: Are we implementing this for `std::array` too? | |||||||||||||
I am not sure if it is going to be very useful. The main use case (at least for the purpose of p2321r2) for these const overloads of assignment operators is that we can assign const tuple of reference types. (well, the const value types usually cannot be assigned to). I don't think std::array supports references huixie90: I am not sure if it is going to be very useful. The main use case (at least for the purpose of… | |||||||||||||
Also, note that we don't support assigning tuple = array anymore. This was an extension, but it was removed. ldionne: Also, note that we don't support assigning `tuple = array` anymore. This was an extension, but… | |||||||||||||
_NOEXCEPT_((_And<is_nothrow_move_assignable<_Tp>...>::value)) | _NOEXCEPT_((_And<is_nothrow_move_assignable<_Tp>...>::value)) | ||||||||||||
{ | { | ||||||||||||
_VSTD::__memberwise_forward_assign(*this, _VSTD::move(__tuple), | _VSTD::__memberwise_forward_assign(*this, _VSTD::move(__tuple), | ||||||||||||
__tuple_types<_Tp...>(), | __tuple_types<_Tp...>(), | ||||||||||||
typename __make_tuple_indices<sizeof...(_Tp)>::type()); | typename __make_tuple_indices<sizeof...(_Tp)>::type()); | ||||||||||||
return *this; | return *this; | ||||||||||||
} | } | ||||||||||||
Show All 23 Lines | tuple& operator=(tuple<_Up...>&& __tuple) | ||||||||||||
_NOEXCEPT_((_And<is_nothrow_assignable<_Tp&, _Up>...>::value)) | _NOEXCEPT_((_And<is_nothrow_assignable<_Tp&, _Up>...>::value)) | ||||||||||||
{ | { | ||||||||||||
_VSTD::__memberwise_forward_assign(*this, _VSTD::move(__tuple), | _VSTD::__memberwise_forward_assign(*this, _VSTD::move(__tuple), | ||||||||||||
__tuple_types<_Up...>(), | __tuple_types<_Up...>(), | ||||||||||||
typename __make_tuple_indices<sizeof...(_Tp)>::type()); | typename __make_tuple_indices<sizeof...(_Tp)>::type()); | ||||||||||||
return *this; | return *this; | ||||||||||||
} | } | ||||||||||||
template<class _Up1, class _Up2, class _Dep = true_type, __enable_if_t< | |||||||||||||
_And<_Dep, | #if _LIBCPP_STD_VER > 20 | ||||||||||||
_BoolConstant<sizeof...(_Tp) == 2>, | template <class... _UTypes, enable_if_t< | ||||||||||||
is_assignable<_FirstType<_Tp..., _Dep>&, _Up1 const&>, | _And<_BoolConstant<sizeof...(_Tp) == sizeof...(_UTypes)>, | ||||||||||||
I like that you use _BoolConstant<sizeof...(_Tp) == sizeof...(_UTypes)> for self-documentation of the condition, however technically it is not needed because is_assignable<const _Tp&, const _UTypes&>... would SFINAE-away if that were not the case. I suggest you leave as-is for documentation purposes, I just wanted to point it out. ldionne: I like that you use `_BoolConstant<sizeof...(_Tp) == sizeof...(_UTypes)>` for self… | |||||||||||||
is_assignable<_SecondType<_Tp..., _Dep>&, _Up2 const&> | is_assignable<const _Tp&, const _UTypes&>...>::value>* = nullptr> | ||||||||||||
>::value | _LIBCPP_HIDE_FROM_ABI constexpr | ||||||||||||
const tuple& operator=(const tuple<_UTypes...>& __u) const { | |||||||||||||
std::__memberwise_copy_assign(*this, | |||||||||||||
__u, | |||||||||||||
typename __make_tuple_indices<sizeof...(_Tp)>::type()); | |||||||||||||
return *this; | |||||||||||||
} | |||||||||||||
template <class... _UTypes, enable_if_t< | |||||||||||||
_And<_BoolConstant<sizeof...(_Tp) == sizeof...(_UTypes)>, | |||||||||||||
is_assignable<const _Tp&, _UTypes>...>::value>* = nullptr> | |||||||||||||
_LIBCPP_HIDE_FROM_ABI constexpr | |||||||||||||
const tuple& operator=(tuple<_UTypes...>&& __u) const { | |||||||||||||
std::__memberwise_forward_assign(*this, | |||||||||||||
__u, | |||||||||||||
__tuple_types<_UTypes...>(), | |||||||||||||
typename __make_tuple_indices<sizeof...(_Tp)>::type()); | |||||||||||||
return *this; | |||||||||||||
} | |||||||||||||
#endif // _LIBCPP_STD_VER > 20 | |||||||||||||
template <template<class...> class Pred, bool _Const, | |||||||||||||
class _Pair, class _DecayedPair = __uncvref_t<_Pair>, class _Tuple = tuple> | |||||||||||||
struct _AssignPredicateFromPair : false_type {}; | |||||||||||||
template <template<class...> class Pred, bool _Const, | |||||||||||||
class _Pair, class _Up1, class _Up2, class _Tp1, class _Tp2> | |||||||||||||
struct _AssignPredicateFromPair<Pred, _Const, _Pair, pair<_Up1, _Up2>, tuple<_Tp1, _Tp2> > : | |||||||||||||
I think we should either use _EnableAssignFromPair with the const and the non-const overloads, or simplify _EnableAssignFromPair to always assume that _Const == true. Otherwise, this code looks buggy, even though it isn't. ldionne: I think we should either use `_EnableAssignFromPair` with the const **and** the non-const… | |||||||||||||
I did the former as refactoring the non-const allows me to completely remove _FirstType huixie90: I did the former as refactoring the non-const allows me to completely remove _FirstType | |||||||||||||
_And<Pred<__maybe_const<_Const, _Tp1>&, __copy_cvref_t<_Pair, _Up1> >, | |||||||||||||
Pred<__maybe_const<_Const, _Tp2>&, __copy_cvref_t<_Pair, _Up2> > | |||||||||||||
> {}; | |||||||||||||
template <bool _Const, class _Pair> | |||||||||||||
struct _EnableAssignFromPair : _AssignPredicateFromPair<is_assignable, _Const, _Pair> {}; | |||||||||||||
template <bool _Const, class _Pair> | |||||||||||||
struct _NothrowAssignFromPair : _AssignPredicateFromPair<is_nothrow_assignable, _Const, _Pair> {}; | |||||||||||||
#if _LIBCPP_STD_VER > 20 | |||||||||||||
template <class _U1, class _U2, enable_if_t< | |||||||||||||
_EnableAssignFromPair<true, const pair<_U1, _U2>&>::value>* = nullptr> | |||||||||||||
_LIBCPP_HIDE_FROM_ABI constexpr | |||||||||||||
const tuple& operator=(const pair<_U1, _U2>& __pair) const | |||||||||||||
noexcept(_NothrowAssignFromPair<true, const pair<_U1, _U2>&>::value) { | |||||||||||||
std::get<0>(*this) = __pair.first; | |||||||||||||
std::get<1>(*this) = __pair.second; | |||||||||||||
return *this; | |||||||||||||
} | |||||||||||||
template <class _U1, class _U2, enable_if_t< | |||||||||||||
_EnableAssignFromPair<true, pair<_U1, _U2>&&>::value>* = nullptr> | |||||||||||||
_LIBCPP_HIDE_FROM_ABI constexpr | |||||||||||||
const tuple& operator=(pair<_U1, _U2>&& __pair) const | |||||||||||||
noexcept(_NothrowAssignFromPair<true, pair<_U1, _U2>&&>::value) { | |||||||||||||
std::get<0>(*this) = std::move(__pair.first); | |||||||||||||
std::get<1>(*this) = std::move(__pair.second); | |||||||||||||
return *this; | |||||||||||||
} | |||||||||||||
#endif // _LIBCPP_STD_VER > 20 | |||||||||||||
template<class _Up1, class _Up2, __enable_if_t< | |||||||||||||
_EnableAssignFromPair<false, pair<_Up1, _Up2> const&>::value | |||||||||||||
,int> = 0> | ,int> = 0> | ||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17 | _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17 | ||||||||||||
tuple& operator=(pair<_Up1, _Up2> const& __pair) | tuple& operator=(pair<_Up1, _Up2> const& __pair) | ||||||||||||
_NOEXCEPT_((_And< | _NOEXCEPT_((_NothrowAssignFromPair<false, pair<_Up1, _Up2> const&>::value)) | ||||||||||||
is_nothrow_assignable<_FirstType<_Tp...>&, _Up1 const&>, | |||||||||||||
is_nothrow_assignable<_SecondType<_Tp...>&, _Up2 const&> | |||||||||||||
>::value)) | |||||||||||||
{ | { | ||||||||||||
_VSTD::get<0>(*this) = __pair.first; | _VSTD::get<0>(*this) = __pair.first; | ||||||||||||
_VSTD::get<1>(*this) = __pair.second; | _VSTD::get<1>(*this) = __pair.second; | ||||||||||||
return *this; | return *this; | ||||||||||||
} | } | ||||||||||||
template<class _Up1, class _Up2, class _Dep = true_type, __enable_if_t< | template<class _Up1, class _Up2, __enable_if_t< | ||||||||||||
_And<_Dep, | _EnableAssignFromPair<false, pair<_Up1, _Up2>&&>::value | ||||||||||||
_BoolConstant<sizeof...(_Tp) == 2>, | |||||||||||||
is_assignable<_FirstType<_Tp..., _Dep>&, _Up1>, | |||||||||||||
is_assignable<_SecondType<_Tp..., _Dep>&, _Up2> | |||||||||||||
>::value | |||||||||||||
,int> = 0> | ,int> = 0> | ||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17 | _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17 | ||||||||||||
tuple& operator=(pair<_Up1, _Up2>&& __pair) | tuple& operator=(pair<_Up1, _Up2>&& __pair) | ||||||||||||
_NOEXCEPT_((_And< | _NOEXCEPT_((_NothrowAssignFromPair<false, pair<_Up1, _Up2>&&>::value)) | ||||||||||||
is_nothrow_assignable<_FirstType<_Tp...>&, _Up1>, | |||||||||||||
is_nothrow_assignable<_SecondType<_Tp...>&, _Up2> | |||||||||||||
>::value)) | |||||||||||||
{ | { | ||||||||||||
_VSTD::get<0>(*this) = _VSTD::forward<_Up1>(__pair.first); | _VSTD::get<0>(*this) = _VSTD::forward<_Up1>(__pair.first); | ||||||||||||
_VSTD::get<1>(*this) = _VSTD::forward<_Up2>(__pair.second); | _VSTD::get<1>(*this) = _VSTD::forward<_Up2>(__pair.second); | ||||||||||||
return *this; | return *this; | ||||||||||||
} | } | ||||||||||||
// EXTENSION | // EXTENSION | ||||||||||||
template<class _Up, size_t _Np, class = __enable_if_t< | template<class _Up, size_t _Np, class = __enable_if_t< | ||||||||||||
Show All 27 Lines | tuple& operator=(array<_Up, _Np>&& __array) | ||||||||||||
typename __make_tuple_indices<sizeof...(_Tp)>::type()); | typename __make_tuple_indices<sizeof...(_Tp)>::type()); | ||||||||||||
return *this; | return *this; | ||||||||||||
} | } | ||||||||||||
// [tuple.swap] | // [tuple.swap] | ||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17 | _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17 | ||||||||||||
void swap(tuple& __t) _NOEXCEPT_(__all<__is_nothrow_swappable<_Tp>::value...>::value) | void swap(tuple& __t) _NOEXCEPT_(__all<__is_nothrow_swappable<_Tp>::value...>::value) | ||||||||||||
{__base_.swap(__t.__base_);} | {__base_.swap(__t.__base_);} | ||||||||||||
#if _LIBCPP_STD_VER > 20 | |||||||||||||
_LIBCPP_HIDE_FROM_ABI constexpr | |||||||||||||
void swap(const tuple& __t) const noexcept(__all<is_nothrow_swappable_v<const _Tp&>...>::value) { | |||||||||||||
__base_.swap(__t.__base_); | |||||||||||||
} | |||||||||||||
#endif // _LIBCPP_STD_VER > 20 | |||||||||||||
}; | }; | ||||||||||||
template <> | template <> | ||||||||||||
class _LIBCPP_TEMPLATE_VIS tuple<> | class _LIBCPP_TEMPLATE_VIS tuple<> | ||||||||||||
{ | { | ||||||||||||
public: | public: | ||||||||||||
_LIBCPP_INLINE_VISIBILITY constexpr | _LIBCPP_INLINE_VISIBILITY constexpr | ||||||||||||
tuple() _NOEXCEPT = default; | tuple() _NOEXCEPT = default; | ||||||||||||
template <class _Alloc> | template <class _Alloc> | ||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17 | _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17 | ||||||||||||
tuple(allocator_arg_t, const _Alloc&) _NOEXCEPT {} | tuple(allocator_arg_t, const _Alloc&) _NOEXCEPT {} | ||||||||||||
template <class _Alloc> | template <class _Alloc> | ||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17 | _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17 | ||||||||||||
tuple(allocator_arg_t, const _Alloc&, const tuple&) _NOEXCEPT {} | tuple(allocator_arg_t, const _Alloc&, const tuple&) _NOEXCEPT {} | ||||||||||||
template <class _Up> | template <class _Up> | ||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17 | _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17 | ||||||||||||
tuple(array<_Up, 0>) _NOEXCEPT {} | tuple(array<_Up, 0>) _NOEXCEPT {} | ||||||||||||
template <class _Alloc, class _Up> | template <class _Alloc, class _Up> | ||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17 | _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17 | ||||||||||||
tuple(allocator_arg_t, const _Alloc&, array<_Up, 0>) _NOEXCEPT {} | tuple(allocator_arg_t, const _Alloc&, array<_Up, 0>) _NOEXCEPT {} | ||||||||||||
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17 | _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17 | ||||||||||||
void swap(tuple&) _NOEXCEPT {} | void swap(tuple&) _NOEXCEPT {} | ||||||||||||
#if _LIBCPP_STD_VER > 20 | |||||||||||||
_LIBCPP_HIDE_FROM_ABI constexpr void swap(const tuple&) const noexcept {} | |||||||||||||
#endif | |||||||||||||
IMO it's not necessary for such a small block. ldionne: IMO it's not necessary for such a small block. | |||||||||||||
}; | }; | ||||||||||||
#if _LIBCPP_STD_VER > 20 | #if _LIBCPP_STD_VER > 20 | ||||||||||||
template <class... _TTypes, class... _UTypes, template<class> class _TQual, template<class> class _UQual> | template <class... _TTypes, class... _UTypes, template<class> class _TQual, template<class> class _UQual> | ||||||||||||
requires requires { typename tuple<common_reference_t<_TQual<_TTypes>, _UQual<_UTypes>>...>; } | requires requires { typename tuple<common_reference_t<_TQual<_TTypes>, _UQual<_UTypes>>...>; } | ||||||||||||
struct basic_common_reference<tuple<_TTypes...>, tuple<_UTypes...>, _TQual, _UQual> { | struct basic_common_reference<tuple<_TTypes...>, tuple<_UTypes...>, _TQual, _UQual> { | ||||||||||||
using type = tuple<common_reference_t<_TQual<_TTypes>, _UQual<_UTypes>>...>; | using type = tuple<common_reference_t<_TQual<_TTypes>, _UQual<_UTypes>>...>; | ||||||||||||
}; | }; | ||||||||||||
Show All 24 Lines | |||||||||||||
< | < | ||||||||||||
__all<__is_swappable<_Tp>::value...>::value, | __all<__is_swappable<_Tp>::value...>::value, | ||||||||||||
void | void | ||||||||||||
>::type | >::type | ||||||||||||
swap(tuple<_Tp...>& __t, tuple<_Tp...>& __u) | swap(tuple<_Tp...>& __t, tuple<_Tp...>& __u) | ||||||||||||
_NOEXCEPT_(__all<__is_nothrow_swappable<_Tp>::value...>::value) | _NOEXCEPT_(__all<__is_nothrow_swappable<_Tp>::value...>::value) | ||||||||||||
{__t.swap(__u);} | {__t.swap(__u);} | ||||||||||||
#if _LIBCPP_STD_VER > 20 | |||||||||||||
template <class... _Tp> | |||||||||||||
_LIBCPP_HIDE_FROM_ABI constexpr | |||||||||||||
enable_if_t<__all<is_swappable_v<const _Tp>...>::value, void> | |||||||||||||
swap(const tuple<_Tp...>& __lhs, const tuple<_Tp...>& __rhs) | |||||||||||||
noexcept(__all<is_nothrow_swappable_v<const _Tp>...>::value) { | |||||||||||||
What's with the reference here EricWF: What's with the reference here | |||||||||||||
__lhs.swap(__rhs); | |||||||||||||
} | |||||||||||||
#endif | |||||||||||||
Ditto. ldionne: Ditto. | |||||||||||||
// get | // get | ||||||||||||
template <size_t _Ip, class ..._Tp> | template <size_t _Ip, class ..._Tp> | ||||||||||||
inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 | inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11 | ||||||||||||
typename tuple_element<_Ip, tuple<_Tp...> >::type& | typename tuple_element<_Ip, tuple<_Tp...> >::type& | ||||||||||||
get(tuple<_Tp...>& __t) _NOEXCEPT | get(tuple<_Tp...>& __t) _NOEXCEPT | ||||||||||||
{ | { | ||||||||||||
typedef _LIBCPP_NODEBUG typename tuple_element<_Ip, tuple<_Tp...> >::type type; | typedef _LIBCPP_NODEBUG typename tuple_element<_Ip, tuple<_Tp...> >::type type; | ||||||||||||
▲ Show 20 Lines • Show All 472 Lines • Show Last 20 Lines |
Nit, but let's be consistent. Here and below.