On Wed, 24 Jan 2024, Jonathan Wakely wrote:

> On Tue, 23 Jan 2024 at 23:54, Patrick Palka wrote:
> > diff --git a/libstdc++-v3/include/bits/stl_pair.h 
> > b/libstdc++-v3/include/bits/stl_pair.h
> > index b81b479ad43..a9b20fbe7ca 100644
> > --- a/libstdc++-v3/include/bits/stl_pair.h
> > +++ b/libstdc++-v3/include/bits/stl_pair.h
> > @@ -85,12 +85,70 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >    /// @cond undocumented
> >
> >    // Forward declarations.
> > +  template<typename, typename>
> > +    struct pair;
> 
> We have a compiler bug where a forward declaration without template
> parameter names causes bad diagnostics later. The compiler seems to
> try to use the parameter names from the first decl it sees, so we end
> up with things like <template-argument-1-1> even when there's a name
> available at the site of the actual error. So I think we should name
> these _T1 and _T2 here.

Will fix.

> 
> > +
> >    template<typename...>
> >      class tuple;
> >
> > +  // Declarations of std::array and its std::get overloads, so that
> > +  // std::tuple_cat can use them if <tuple> is included before <array>.
> > +  // We also declare the other std::get overloads here so that they're
> > +  // visible to the P2165R4 tuple-like constructors of pair and tuple.
> > +  template<typename _Tp, size_t _Nm>
> > +    struct array;
> > +
> >    template<size_t...>
> >      struct _Index_tuple;
> >
> > +  template<size_t _Int, class _Tp1, class _Tp2>
> > +    constexpr typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&
> > +    get(pair<_Tp1, _Tp2>& __in) noexcept;
> > +
> > +  template<size_t _Int, class _Tp1, class _Tp2>
> > +    constexpr typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&&
> > +    get(pair<_Tp1, _Tp2>&& __in) noexcept;
> > +
> > +  template<size_t _Int, class _Tp1, class _Tp2>
> > +    constexpr const typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&
> > +    get(const pair<_Tp1, _Tp2>& __in) noexcept;
> > +
> > +  template<size_t _Int, class _Tp1, class _Tp2>
> > +    constexpr const typename tuple_element<_Int, pair<_Tp1, _Tp2>>::type&&
> > +    get(const pair<_Tp1, _Tp2>&& __in) noexcept;
> > +
> > +  template<size_t __i, typename... _Elements>
> > +    constexpr __tuple_element_t<__i, tuple<_Elements...>>&
> > +    get(tuple<_Elements...>& __t) noexcept;
> > +
> > +  template<size_t __i, typename... _Elements>
> > +    constexpr const __tuple_element_t<__i, tuple<_Elements...>>&
> > +    get(const tuple<_Elements...>& __t) noexcept;
> > +
> > +  template<size_t __i, typename... _Elements>
> > +    constexpr __tuple_element_t<__i, tuple<_Elements...>>&&
> > +    get(tuple<_Elements...>&& __t) noexcept;
> > +
> > +  template<size_t __i, typename... _Elements>
> > +    constexpr const __tuple_element_t<__i, tuple<_Elements...>>&&
> > +    get(const tuple<_Elements...>&& __t) noexcept;
> > +
> > +  template<size_t _Int, typename _Tp, size_t _Nm>
> > +    constexpr _Tp&
> > +    get(array<_Tp, _Nm>&) noexcept;
> > +
> > +  template<size_t _Int, typename _Tp, size_t _Nm>
> > +    constexpr _Tp&&
> > +    get(array<_Tp, _Nm>&&) noexcept;
> > +
> > +  template<size_t _Int, typename _Tp, size_t _Nm>
> > +    constexpr const _Tp&
> > +    get(const array<_Tp, _Nm>&) noexcept;
> > +
> > +  template<size_t _Int, typename _Tp, size_t _Nm>
> > +    constexpr const _Tp&&
> > +    get(const array<_Tp, _Nm>&&) noexcept;
> > +
> >  #if ! __cpp_lib_concepts
> >    // Concept utility functions, reused in conditionally-explicit
> >    // constructors.
> > @@ -159,6 +217,46 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >  #endif // lib concepts
> >  #endif // C++11
> >
> > +#if __glibcxx_tuple_like // >= C++23
> > +  template<typename _Tp>
> > +    inline constexpr bool __is_tuple_v = false;
> > +
> > +  template<typename... _Ts>
> > +    inline constexpr bool __is_tuple_v<tuple<_Ts...>> = true;
> > +
> > +  // TODO: Reuse __is_tuple_like from <type_traits>?
> > +  template<typename _Tp>
> > +    inline constexpr bool __is_tuple_like_v = false;
> > +
> > +  template<typename... _Elements>
> > +    inline constexpr bool __is_tuple_like_v<tuple<_Elements...>> = true;
> > +
> > +  template<typename _T1, typename _T2>
> > +    inline constexpr bool __is_tuple_like_v<pair<_T1, _T2>> = true;
> > +
> > +  template<typename _Tp, size_t _Nm>
> > +    inline constexpr bool __is_tuple_like_v<array<_Tp, _Nm>> = true;
> > +
> > +  // __is_tuple_like_v<subrange> is defined in <bits/ranges_util.h>.
> > +
> > +  template<typename _Tp>
> > +    concept __tuple_like = __is_tuple_like_v<remove_cvref_t<_Tp>>;
> > +
> > +  template<typename _Tp>
> > +    concept __pair_like = __tuple_like<_Tp> && 
> > tuple_size_v<remove_cvref_t<_Tp>> == 2;
> > +
> > +  template<typename _Tp, typename _Tuple>
> > +    concept __eligible_tuple_like
> > +      = __detail::__different_from<_Tp, _Tuple> && __tuple_like<_Tp>
> > +       && (tuple_size_v<remove_cvref_t<_Tp>> == tuple_size_v<_Tuple>)
> > +       && !ranges::__detail::__is_subrange<remove_cvref_t<_Tp>>;
> > +
> > +  template<typename _Tp, typename _Pair>
> > +    concept __eligible_pair_like
> > +      = __detail::__different_from<_Tp, _Pair> && __pair_like<_Tp>
> > +       && !ranges::__detail::__is_subrange<remove_cvref_t<_Tp>>;
> > +#endif // C++23
> > +
> >    template<typename _U1, typename _U2> class __pair_base
> >    {
> >  #if __cplusplus >= 201103L && ! __cpp_lib_concepts
> > @@ -295,6 +393,24 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >           return false;
> >  #endif
> >         }
> > +
> > +#if __glibcxx_tuple_like // >= C++23
> > +      template<typename _UPair>
> > +       static constexpr bool
> > +       _S_constructible_from_pair_like()
> > +       {
> > +         return 
> > _S_constructible<decltype(std::get<0>(std::declval<_UPair>())),
> > +                                 
> > decltype(std::get<1>(std::declval<_UPair>()))>();
> > +       }
> > +
> > +      template<typename _UPair>
> > +       static constexpr bool
> > +       _S_convertible_from_pair_like()
> > +       {
> > +         return 
> > _S_convertible<decltype(std::get<0>(std::declval<_UPair>())),
> > +                               
> > decltype(std::get<1>(std::declval<_UPair>()))>();
> > +       }
> > +#endif // C++23
> >        /// @endcond
> >
> >      public:
> > @@ -393,6 +509,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >         pair(const pair<_U1, _U2>&&) = delete;
> >  #endif // C++23
> >
> > +#if __glibcxx_tuple_like // >= C++23
> > +      template<__eligible_pair_like<pair> _UPair>
> > +       requires (_S_constructible_from_pair_like<_UPair>())
> > +       constexpr explicit(!_S_convertible_from_pair_like<_UPair>())
> > +       pair(_UPair&& __p)
> > +       : first(std::get<0>(std::forward<_UPair>(__p))),
> > +         second(std::get<1>(std::forward<_UPair>(__p)))
> > +       { }
> > +#endif // C++23
> 
> I think this needs to be constrained with !_S_dangles<...>() and we
> need a deleted overload with the same constraints, except for
> _S_dangles being true.
> 
> And that should be covered by a test.

Oops, will fix.  Should the deleted overloads carry over the
conditionally explicit specifier?  I noticed pair's deleted overloads
do, but tuple's overloads don't.

> 
> 
> 
> > diff --git a/libstdc++-v3/include/std/tuple b/libstdc++-v3/include/std/tuple
> > index be92f1eb973..182f3cc5e6a 100644
> > --- a/libstdc++-v3/include/std/tuple
> > +++ b/libstdc++-v3/include/std/tuple
> > @@ -50,6 +50,7 @@
> >  #define __glibcxx_want_apply
> >  #define __glibcxx_want_make_from_tuple
> >  #define __glibcxx_want_ranges_zip
> > +#define __glibcxx_want_tuple_like
> >  #include <bits/version.h>
> >
> >  namespace std _GLIBCXX_VISIBILITY(default)
> > @@ -246,6 +247,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >        _Head _M_head_impl;
> >      };
> >
> > +#if __cpp_lib_tuple_like // >= C++23
> > +  struct __tuple_like_tag_t { explicit __tuple_like_tag_t() = default; };
> > +
> > +  // Forward declared for use by the operator<=> overload for tuple-like 
> > types.
> > +  template<typename _Cat, typename _Tp, typename _Up>
> > +    constexpr _Cat
> > +    __tuple_cmp(const _Tp&, const _Up&, index_sequence<>);
> > +
> > +  template<typename _Cat, typename _Tp, typename _Up,
> > +          size_t _Idx0, size_t... _Idxs>
> > +    constexpr _Cat
> > +    __tuple_cmp(const _Tp& __t, const _Up& __u,
> > +               index_sequence<_Idx0, _Idxs...>);
> > +#endif // C++23
> > +
> >    /**
> >     * Contains the actual implementation of the @c tuple template, stored
> >     * as a recursive inheritance hierarchy from the first element (most
> > @@ -342,6 +358,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >         { }
> >  #endif // C++23
> >
> > +#if __cpp_lib_tuple_like // >= C++23
> > +      template<typename _UTuple, size_t... _Is>
> > +       constexpr
> > +       _Tuple_impl(__tuple_like_tag_t, _UTuple&& __u, 
> > index_sequence<_Is...>)
> > +       : _Tuple_impl(std::get<_Is>(std::forward<_UTuple>(__u))...)
> > +       { }
> > +#endif // C++23
> > +
> >        template<typename _Alloc>
> >         _GLIBCXX20_CONSTEXPR
> >         _Tuple_impl(allocator_arg_t __tag, const _Alloc& __a)
> > @@ -428,6 +452,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >         { }
> >  #endif // C++23
> >
> > +#if __cpp_lib_tuple_like // >= C++23
> > +      template<typename _Alloc, typename _UTuple, size_t... _Is>
> > +       constexpr
> > +       _Tuple_impl(__tuple_like_tag_t, allocator_arg_t __tag, const 
> > _Alloc& __a,
> > +                   _UTuple&& __u, index_sequence<_Is...>)
> > +       : _Tuple_impl(__tag, __a, 
> > std::get<_Is>(std::forward<_UTuple>(__u))...)
> > +       { }
> > +#endif // C++23
> > +
> >        template<typename... _UElements>
> >         _GLIBCXX20_CONSTEXPR
> >         void
> > @@ -470,6 +503,24 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >         }
> >  #endif // C++23
> >
> > +#if __cpp_lib_tuple_like // >= C++23
> > +      template<typename _UTuple>
> > +       constexpr void
> > +       _M_assign(__tuple_like_tag_t __tag, _UTuple&& __u)
> > +       {
> > +         _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u));
> > +         _M_tail(*this)._M_assign(__tag, std::forward<_UTuple>(__u));
> > +       }
> > +
> > +      template<typename _UTuple>
> > +       constexpr void
> > +       _M_assign(__tuple_like_tag_t __tag, _UTuple&& __u) const
> > +       {
> > +         _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u));
> > +         _M_tail(*this)._M_assign(__tag, std::forward<_UTuple>(__u));
> > +       }
> > +#endif // C++23
> > +
> >      protected:
> >        _GLIBCXX20_CONSTEXPR
> >        void
> > @@ -563,6 +614,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >         { }
> >  #endif // C++23
> >
> > +#if __cpp_lib_tuple_like // >= C++23
> > +      template<typename _UTuple>
> > +       constexpr
> > +       _Tuple_impl(__tuple_like_tag_t, _UTuple&& __u, index_sequence<0>)
> > +       : _Tuple_impl(std::get<0>(std::forward<_UTuple>(__u)))
> > +       { }
> > +#endif // C++23
> > +
> >        template<typename _Alloc>
> >         _GLIBCXX20_CONSTEXPR
> >         _Tuple_impl(allocator_arg_t __tag, const _Alloc& __a)
> > @@ -633,6 +692,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >         { }
> >  #endif // C++23
> >
> > +#if __cpp_lib_tuple_like // >= C++23
> > +      template<typename _Alloc, typename _UTuple>
> > +       constexpr
> > +       _Tuple_impl(__tuple_like_tag_t, allocator_arg_t __tag, const 
> > _Alloc& __a,
> > +                   _UTuple&& __u, index_sequence<0>)
> > +       : _Tuple_impl(__tag, __a, std::get<0>(std::forward<_UTuple>(__u)))
> > +       { }
> > +#endif // C++23
> > +
> >        template<typename _UHead>
> >         _GLIBCXX20_CONSTEXPR
> >         void
> > @@ -667,6 +735,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >         }
> >  #endif // C++23
> >
> > +#if __cpp_lib_tuple_like // >= C++23
> > +    template<typename _UTuple>
> > +      constexpr void
> > +      _M_assign(__tuple_like_tag_t, _UTuple&& __u)
> > +      { _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u)); }
> > +
> > +    template<typename _UTuple>
> > +      constexpr void
> > +      _M_assign(__tuple_like_tag_t, _UTuple&& __u) const
> > +      { _M_head(*this) = std::get<_Idx>(std::forward<_UTuple>(__u)); }
> > +#endif // C++23
> > +
> >      protected:
> >        _GLIBCXX20_CONSTEXPR
> >        void
> > @@ -846,6 +926,26 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >  #endif
> >         }
> >
> > +#if __cpp_lib_tuple_like // >= C++23
> > +      template<typename _UTuple>
> > +       static consteval bool
> > +       __constructible_from_tuple_like()
> > +       {
> > +         return []<size_t... _Is>(index_sequence<_Is...>) {
> > +           return 
> > __constructible<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
> > +         }(make_index_sequence<sizeof...(_Elements)>{});
> > +       }
> > +
> > +      template<typename _UTuple>
> > +       static consteval bool
> > +       __convertible_from_tuple_like()
> > +       {
> > +         return []<size_t... _Is>(index_sequence<_Is...>) {
> > +           return 
> > __convertible<decltype(std::get<_Is>(std::declval<_UTuple>()))...>();
> > +         }(make_index_sequence<sizeof...(_Elements)>{});
> 
> These new functions can use index_sequence_for<_Elements...>{} here,
> so you don't need the sizeof....
> That applies several times below as well.
> 
> I think it's semantically identical, just a little shorter. I don't
> know if there's any compilation speed benefit either way. Maybe
> sizeof...(_Elements) is cheaper than expanding the pack into the
> index_sequence_for alias template?
> 
> 
> > +       }
> > +#endif // C++23
> > +
> >      public:
> >        constexpr
> >        explicit(!(__is_implicitly_default_constructible_v<_Elements> && 
> > ...))
> > @@ -1016,10 +1116,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >         tuple(const pair<_U1, _U2>&&) = delete;
> >  #endif // C++23
> >
> > -#if 0 && __cpp_lib_tuple_like // >= C++23
> > -      template<__tuple_like _UTuple>
> > -       constexpr explicit(...)
> > -       tuple(_UTuple&& __u);
> > +#if __cpp_lib_tuple_like // >= C++23
> > +      template<__eligible_tuple_like<tuple> _UTuple>
> > +       requires (__constructible_from_tuple_like<_UTuple>())
> > +         && (!__use_other_ctor<_UTuple>())
> > +       constexpr explicit(!__convertible_from_tuple_like<_UTuple>())
> > +       tuple(_UTuple&& __u)
> > +       : _Inherited(__tuple_like_tag_t{},
> > +                    std::forward<_UTuple>(__u),
> > +                    make_index_sequence<sizeof...(_Elements)>{})
> > +       { }
> >  #endif // C++23
> >
> >        // Allocator-extended constructors.
> > @@ -1202,10 +1308,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >         tuple(allocator_arg_t, const _Alloc&, const pair<_U1, _U2>&&) = 
> > delete;
> >  #endif // C++23
> >
> > -#if 0 && __cpp_lib_tuple_like // >= C++23
> > -      template<typename _Alloc, __tuple_like _UTuple>
> > -       constexpr explicit(...)
> > -       tuple(allocator_arg_t __tag, const _Alloc& __a, _UTuple&& __u);
> > +#if __cpp_lib_tuple_like // >= C++23
> > +      template<typename _Alloc, __eligible_tuple_like<tuple> _UTuple>
> > +       requires (__constructible_from_tuple_like<_UTuple>())
> > +         && (!__use_other_ctor<_UTuple>())
> > +       constexpr explicit(!__convertible_from_tuple_like<_UTuple>())
> > +       tuple(allocator_arg_t __tag, const _Alloc& __a, _UTuple&& __u)
> > +       : _Inherited(__tuple_like_tag_t{},
> > +                    __tag, __a, std::forward<_UTuple>(__u),
> > +                    make_index_sequence<sizeof...(_Elements)>{})
> > +       { }
> >  #endif // C++23
> 
> For some reason these two new constructors aren't deleted if they
> create dangling refs. I don't know why.

Hmm, seems like an oversight.  Shall we proactively implement them?

Reply via email to