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 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 during the copy/move constructibility
check.  Considering this constructor is ultimately 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 of this tuple type.

Changing the template-ness of a constructor can affect 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.

[1]: See r11-7287-g187d0d5871b1fa and
https://isocpp.org/files/papers/P3606R0.html

	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.

Reviewed-by: Jonathan Wakely <jwakely@redhat.com>
This commit is contained in:
Patrick Palka 2025-03-13 19:55:00 -04:00
parent ab4e9fd943
commit 6570fa6f26
2 changed files with 37 additions and 6 deletions

View file

@ -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...>())

View file

@ -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);