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 > >