On Thu, 13 Mar 2025, Patrick Palka wrote:

> Tested on x86_64-pc-linux-gnu, does this look OK for trunk, and 14 after
> a while?
> 
> -- >8 --
> 
> 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 (and 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 tuple
> bug (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 cause overload resolution ambiguities.  The tuple
> constructor set doesn't seem particularly reliant on this tiebreaker
> though.

Ah, I just realized the C++17 tuple impl already defines the
tuple(const _Elements&...) constructor as a template in order to
constrain it!  So this patch arguably makes the C++20 constructor
set more consistent with the C++17 impl, and so should 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&...)):
>       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..67760471d95 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<class...>

Whoops, s/class/typename as per convention.

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