On Thu, 13 Mar 2025, 22:28 Patrick Palka, <ppa...@redhat.com> wrote:

> On Thu, 13 Mar 2025, Jonathan Wakely wrote:
>
> > On Thu, 13 Mar 2025 at 21:29, Patrick Palka <ppa...@redhat.com> wrote:
> > >
> > > On Thu, 13 Mar 2025, Ville Voutilainen wrote:
> > >
> > > > On Thu, 13 Mar 2025 at 23:16, Ville Voutilainen
> > > > <ville.voutilai...@gmail.com> wrote:
> > > > >
> > > > > On Thu, 13 Mar 2025 at 23:03, Patrick Palka <ppa...@redhat.com>
> wrote:
> > > > > > +      // Defined as a template to work around PR
> libstdc++/116440.
> > > > > > +      template<class...>
> > > > > > +       constexpr explicit(!__convertible<const _Elements&...>())
> > > > > > +       tuple(const _Elements&... __elements)
> > > > >
> > > > > I don't understand how a constructor template declared like this
> can
> > > > > ever be called. The template parameter pack
> > > > > can't be provided or deduced, and can't have a default. So we're
> > > > > effectively making this signature always lose
> > > > > overload resolution to the one that takes a pack of _UElements&&.
> > > > >
> > > > > Which may be fine. I can't head-compile a test that would fail in
> that
> > > > > case. If any of the incoming argument isn't one
> > > > > of _Elements, that constructor wins overload resolution anyway. If
> the
> > > > > incoming arguments are exactly _Elements, that
> > > > > constructor does the same thing as this one. I think.
> > > >
> > > > Oh, never mind. The pack is just deduced as an empty pack.
> > >
> > > Yep that's my understanding, though I don't know where in the standard
> > > this is specified, a quick Ctrl+F is failing me.
> > >
> > > I can use template<int = 0> or template<typename = void> if that's
> > > preferred :)
> >
> > I would prefer template<typename = void> to the empty pack, I think
> > the default template argument makes it a little more obvious how that
> > constructor can be called (I'm sure Ville won't be the only one to
> > raise an eyebrow at that).
>
> Fixed.
>
> >
> > Thanks for figuring this out, and noticing that that the template-ness
> > of that constructor is what changed between C++17 and C++20. I think
> > when I re-implemented it using concepts I assumed the template-ness
> > was there for the _ImplicitCtor / _ExplicitCtor stuff, which is done
> > using explicit(bool) in C++20. I wasn't looking at the tuple(const
> > _Elements&...) constructor at all, because the errors all pointed to
> > tuple(_UTypes&&...).
>
> Yeah, I had to whip out GDB in order to find this hidden instantiation
> context.  We do eventually recurse into the _Utypes&&... constructor,
> but it's apparently not the start of it.
>
> >
> > Do we also want to constraint the tuple(const _Elements&...)
> > constructor with requires sizeof...(_Elements) >= 1, which is present
> > on the C++17 version?
>
> I guess we should constrain the corresponding allocator-aware
> constructor too at the same time.  But it does seem the constraint
> isn't necessary (at least nowadays) due to the explicit specialization,
> so I haven't added it.
>
> Updated patch using 'typename = void':
>

OK for trunk and after a bit of time, 14. Thanks!



> -- >8 --
>
> Subject: [PATCH v2] libstdc++: Work around C++20 tuple<tuple<any>>
> constraint
>  recursion [PR116440]
>
> The type tuple<tuple<any>> is clearly copy/move constructible, but for
> reasons that are not yet completely understood checking this property
> triggers constraint recursion with our C++20 tuple implementation (but
> not the C++17 implementation).
>
> It turns out this recursion stems from considering the non-template
> tuple(const _Elements&) constructor when checking for copy/move
> constructibility.  Checking this constructor is of course redundant,
> since the defaulted copy/move constructors are better matches.
>
> GCC has a non-standard "perfect candidate" optimization[1] that causes
> overload resolution to shortcut considering template candidates if we
> find a (non-template) perfect candidate.  So to work around this issue
> (and as a general compile-time optimization) this patch turns the
> problematic constructor into a template so that GCC doesn't consider it
> when checking for copy/move constructibility.
>
> Changing the template-ness of a constructor can affect the outcome of
> overload resolution (since template-ness is a tiebreaker) so there's a
> risk this change could e.g. introduce overload resolution ambiguities.
> But the original C++17 implementation has long defined this constructor
> as a template (in order to constrain it etc), so doing the same thing
> in the C++20 mode should naturally be quite safe.
>
> The testcase still fails with Clang (in C++20 mode) since it doesn't
> implement said optimization.
>
>         PR libstdc++/116440
>
> libstdc++-v3/ChangeLog:
>
>         * include/std/tuple (tuple::tuple(const _Elements&...))
>         [C++20]: Turn into a template.
>         * testsuite/20_util/tuple/116440.C: New test.
>
> [1]: See r11-7287-g187d0d5871b1fa and
> https://isocpp.org/files/papers/P3606R0.html
> ---
>  libstdc++-v3/include/std/tuple                | 14 +++++----
>  libstdc++-v3/testsuite/20_util/tuple/116440.C | 29 +++++++++++++++++++
>  2 files changed, 37 insertions(+), 6 deletions(-)
>  create mode 100644 libstdc++-v3/testsuite/20_util/tuple/116440.C
>
> diff --git a/libstdc++-v3/include/std/tuple
> b/libstdc++-v3/include/std/tuple
> index 34d790fd6f5..d3deb7bc124 100644
> --- a/libstdc++-v3/include/std/tuple
> +++ b/libstdc++-v3/include/std/tuple
> @@ -966,12 +966,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>        : _Inherited()
>        { }
>
> -      constexpr explicit(!__convertible<const _Elements&...>())
> -      tuple(const _Elements&... __elements)
> -      noexcept(__nothrow_constructible<const _Elements&...>())
> -      requires (__constructible<const _Elements&...>())
> -      : _Inherited(__elements...)
> -      { }
> +      // Defined as a template to work around PR libstdc++/116440.
> +      template<typename = void>
> +       constexpr explicit(!__convertible<const _Elements&...>())
> +       tuple(const _Elements&... __elements)
> +       noexcept(__nothrow_constructible<const _Elements&...>())
> +       requires (__constructible<const _Elements&...>())
> +       : _Inherited(__elements...)
> +       { }
>
>        template<typename... _UTypes>
>         requires (__disambiguating_constraint<_UTypes...>())
> diff --git a/libstdc++-v3/testsuite/20_util/tuple/116440.C
> b/libstdc++-v3/testsuite/20_util/tuple/116440.C
> new file mode 100644
> index 00000000000..12259134d25
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/20_util/tuple/116440.C
> @@ -0,0 +1,29 @@
> +// PR libstdc++/116440 - std::tuple<std::tuple<std::any>> does not compile
> +// { dg-do compile { target c++17 } }
> +
> +#include <any>
> +#include <tuple>
> +#include <type_traits>
> +
> +template <typename T>
> +using TupleTuple = std::tuple<std::tuple<T>>;
> +
> +struct EmbedAny {
> +    std::any content;
> +};
> +
> +static_assert(std::is_copy_constructible<TupleTuple<EmbedAny>>::value);
> +static_assert(std::is_move_constructible<TupleTuple<EmbedAny>>::value);
> +
> +static_assert(std::is_copy_constructible<TupleTuple<std::any>>::value);
> +static_assert(std::is_move_constructible<TupleTuple<std::any>>::value);
> +
> +static_assert(std::is_constructible_v<std::any, TupleTuple<std::any>>);
> +
> +struct EmbedAnyWithZeroSizeArray {
> +    void* pad[0];
> +    std::any content;
> +};
> +
>
> +static_assert(std::is_copy_constructible<TupleTuple<EmbedAnyWithZeroSizeArray>>::value);
>
> +static_assert(std::is_move_constructible<TupleTuple<EmbedAnyWithZeroSizeArray>>::value);
> --
> 2.49.0.rc1.37.ge969bc8759
>
>

Reply via email to