From a00c34bf08052e9659e3eea17ffd28c0864bcb35 Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Sat, 11 Jul 2026 17:46:55 +0100 Subject: [PATCH 1/2] Fix ambiguous partial specialization when appending a `sum` to a `pack` `_pack_append`'s two specializations both matched a sum and neither subsumed the other, so naming `append_type` was ill-formed: gcc hard-errored even inside a requires-expression, while clang answered false. They now exclude each other, and the sum case defines no `type`, making the rejection a clean substitution failure on both compilers. Drops `_append = delete`, which `append`'s trailing return type made unreachable. Closes #282 Assisted-by: Claude:claude-opus-4-8[1m] --- include/fn/detail/pack_impl.hpp | 12 +++++------- tests/fn/pack.cpp | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/include/fn/detail/pack_impl.hpp b/include/fn/detail/pack_impl.hpp index 7049c6fd..a5818d6f 100644 --- a/include/fn/detail/pack_impl.hpp +++ b/include/fn/detail/pack_impl.hpp @@ -24,11 +24,14 @@ template <::std::size_t I, typename T> struct _element { template struct pack_impl; template struct _pack_append; +// A pack never holds a sum. The specialization is defined but has no `type`, so naming +// `append_type` is a substitution failure rather than a use of an incomplete type; and the +// two specializations must exclude each other, or both match a sum and the choice is ambiguous. template requires _some_sum -struct _pack_append; +struct _pack_append {}; template - requires(not _some_pack) + requires(not _some_pack) && (not _some_sum) struct _pack_append { using impl = pack_impl<::std::index_sequence_for, Ts..., T>; using type = ::fn::pack; @@ -98,11 +101,6 @@ struct pack_impl<::std::index_sequence, Ts...> : _element... { return type{static_cast>(FWD(self)._element::v)..., FWD(args)...}; }); } - - template - static constexpr auto _append(Self &&self, auto &&...args) noexcept - requires _some_sum - = delete; }; } // namespace fn::detail diff --git a/tests/fn/pack.cpp b/tests/fn/pack.cpp index b1c31c7f..d74f8f03 100644 --- a/tests/fn/pack.cpp +++ b/tests/fn/pack.cpp @@ -27,6 +27,12 @@ template P> struct pack_nttp final {}; template struct some_pack_nttp final {}; template auto read_nttp() { return fn::get<0>(P); } +template +concept can_append_in_place = requires(V v, Args... args) { FWD(v).append(std::in_place_type, args...); }; + +template +concept can_append = requires(V v, Arg arg) { FWD(v).append(FWD(arg)); }; + } // namespace TEST_CASE("pack", "[pack]") @@ -238,6 +244,21 @@ TEST_CASE("append value categories", "[pack][append]") return i && j == 3 && b1.v == 14 && c.v == 30 && b2.v == 20; })); } + + WHEN("constraints") + { + static_assert(can_append_in_place); + static_assert(can_append_in_place); + static_assert(not can_append_in_place); // B is not constructible from it + + // A pack never holds a sum, in either spelling - and asking must answer, not hard-error + static_assert(not can_append>); + static_assert(not can_append &>); + static_assert(not can_append_in_place, fn::sum>); + static_assert(not can_append_in_place, fn::sum_for>); + + SUCCEED(); + } } TEST_CASE("pack with immovable data", "[pack][immovable]") From c1f74b679e789bb858eebc98d6ec350c5f80a8bd Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Sat, 11 Jul 2026 17:51:55 +0100 Subject: [PATCH 2/2] Reject `in_place_type` as a deduced `pack::append` argument The tag selects the element type; it is never an element. With a type that has no default constructor the in-place overload dropped out on its `is_constructible_v` conjunct and the deduced-argument overload picked the call up, silently appending the tag itself, so `append(in_place_type)` meant "construct a T" or "append the tag" depending on a property of T. The deduced-argument overloads now carry the guard `as_pack` already had, leaving the in-place overload as the only candidate for a tag. Closes #283 Assisted-by: Claude:claude-opus-4-8[1m] --- include/fn/pack.hpp | 12 ++++++++---- tests/fn/pack.cpp | 9 +++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/include/fn/pack.hpp b/include/fn/pack.hpp index a15a5f13..5eea57c4 100644 --- a/include/fn/pack.hpp +++ b/include/fn/pack.hpp @@ -106,7 +106,8 @@ template struct pack : detail::pack_impl<::std::index_sequence_ */ template [[nodiscard]] constexpr auto append(Arg &&arg) & noexcept -> append_type - requires requires { append_type{_impl::template _append(*this, FWD(arg))}; } + requires(not some_in_place_type) + && requires { append_type{_impl::template _append(*this, FWD(arg))}; } { return {_impl::template _append(*this, FWD(arg))}; } @@ -120,7 +121,8 @@ template struct pack : detail::pack_impl<::std::index_sequence_ */ template [[nodiscard]] constexpr auto append(Arg &&arg) const & noexcept -> append_type - requires requires { append_type{_impl::template _append(*this, FWD(arg))}; } + requires(not some_in_place_type) + && requires { append_type{_impl::template _append(*this, FWD(arg))}; } { return {_impl::template _append(*this, FWD(arg))}; } @@ -134,7 +136,8 @@ template struct pack : detail::pack_impl<::std::index_sequence_ */ template [[nodiscard]] constexpr auto append(Arg &&arg) && noexcept -> append_type - requires requires { append_type{_impl::template _append(::std::move(*this), FWD(arg))}; } + requires(not some_in_place_type) + && requires { append_type{_impl::template _append(::std::move(*this), FWD(arg))}; } { return {_impl::template _append(::std::move(*this), FWD(arg))}; } @@ -148,7 +151,8 @@ template struct pack : detail::pack_impl<::std::index_sequence_ */ template [[nodiscard]] constexpr auto append(Arg &&arg) const && noexcept -> append_type - requires requires { append_type{_impl::template _append(::std::move(*this), FWD(arg))}; } + requires(not some_in_place_type) + && requires { append_type{_impl::template _append(::std::move(*this), FWD(arg))}; } { return {_impl::template _append(::std::move(*this), FWD(arg))}; } diff --git a/tests/fn/pack.cpp b/tests/fn/pack.cpp index d74f8f03..c508e89e 100644 --- a/tests/fn/pack.cpp +++ b/tests/fn/pack.cpp @@ -251,6 +251,15 @@ TEST_CASE("append value categories", "[pack][append]") static_assert(can_append_in_place); static_assert(not can_append_in_place); // B is not constructible from it + // in_place_type selects the element type, it is never itself an element: with no arguments and + // no default constructor there is nothing to construct, and the deduced-Arg overload must not + // pick the call up and append the tag instead + static_assert(not can_append_in_place); + static_assert(not can_append const &>); + // C has a default constructor, so the same call still means "construct the element" + static_assert(can_append_in_place); + static_assert(std::same_as().append(std::in_place_type)), T::append_type>); + // A pack never holds a sum, in either spelling - and asking must answer, not hard-error static_assert(not can_append>); static_assert(not can_append &>);