On Wed, 24 Jan 2024, Jonathan Wakely wrote:

> On Wed, 24 Jan 2024 at 15:24, Patrick Palka <ppa...@redhat.com> wrote:
> >
> > 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.
> 
> Huh, oops. Does an explicit ctor participate in overload resolution
> and then get checked if it's usable, or is it remove from overload
> resolution earlier?
> It looks like I decided the answer was the latter for pair and the
> former for tuple.

AFAICT if we know the explicitness of the ctor early (either because the
ctor is a non-template or it has a non-dependent explicit-spec), then we
remove it from the overload set early, in which case it'd be useful to
give the deleted ctor the right explicit-spec to avoid unecessary
constraint checking etc.

Otherwise, for ctor templates with a dependent explicit-spec such as
these tuple/pair ones, we must wait until after deduction to check
explicitness which means constraints are checked first.  And by then
we already know that __dangles is true, so we presumably want overload
resolution to fail regardless.  Whether that's due to selecting a
(viable) deleted non-explicit ctor (if we omit the explicit-spec) or due
to there being no viable non-explicit ctor (if we carry over the
explicit-spec) shouldn't make a difference, I think?

So it seems unnecessary to give these deleted overloads an
explicit-spec; it wouldn't be considered unless overload resolution
is destined to fail anyway.

I'm not totally confident about this assessment though so I'll
just carry over the explicit-spec for now.

> 
> >
> > >
> > >
> > >
> > > > 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?
> 
> Yes, I think so. I can't see why we would want to permit a dangling
> reference there.
> 
> e.g.
> std::array<long, 1> a{};
> std::tuple<const int&> t(a);

Sounds good.

> 
> 

Reply via email to