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

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