Skip to content

pack: appending a sum is an ambiguous partial specialization — hard error on gcc, SFINAE-clean on clang #282

Description

@Bronek

A pack never holds a sumpack.hpp:36 static_asserts it, and pack_impl.hpp:102-105 deletes
_append<T> for _some_sum<T>. But the rejection never reaches either guard, and the two
supported compilers disagree about what happens instead
.

The ambiguity

_pack_append's partial specializations overlap for a sum argument (pack_impl.hpp:26-35):

template <typename T, typename... Ts>
  requires _some_sum<T>
struct _pack_append<T, Ts...>;              // (A) declared, never defined

template <typename T, typename... Ts>
  requires(not _some_pack<T>)
struct _pack_append<T, Ts...> { ... };      // (B) defined — a sum is not a pack, so this matches too

For T = sum<int> both are satisfied and neither constraint subsumes the other, so the partial
specialization is ambiguous ([temp.spec.partial.match]/2 — ill-formed).

The divergence

append's trailing return type is -> append_type<T>, i.e. _pack_append<T, Ts...>::type. A
trailing return type is part of the signature and is substituted before the trailing
requires-clause is checked, so the ambiguity fires first — the deleted _append<sum> overload is
unreachable dead code. (The codebase already knows this hazard: see the comment at
fn/detail/functional.hpp:93-95 explaining why invoke uses a deduced return instead.)

template <typename P, typename T>
concept can_append_in_place = requires(P p, T t) { static_cast<P &&>(p).append(std::in_place_type<T>, t); };

static_assert(can_append_in_place<fn::pack<int> &, int>);            // sanity
static_assert(not can_append_in_place<fn::pack<int> &, fn::sum<int>>);
compiler result
g++ 16.1.1 hard errorambiguous template instantiation for 'struct fn::detail::_pack_append<fn::sum<int>, int>', even inside the requires-expression
clang 22.1.6 compiles; the concept correctly yields false

So on clang the rejection is SFINAE-clean and generic code can ask "can I append this?"; on gcc the
question itself fails to compile. Same source, two answers.

Fix (verified)

Make the two specializations mutually exclusive, and give the sum case a body with no type member
so ::type is a plain substitution failure ("no member named type") rather than a use of an
incomplete type:

template <typename T, typename... Ts>
  requires _some_sum<T>
struct _pack_append<T, Ts...> {};                          // defined, but no `type`

template <typename T, typename... Ts>
  requires(not _some_pack<T>) && (not _some_sum<T>)        // <-- excludes sums
struct _pack_append<T, Ts...> { ... };

With this, both compilers accept the probe above and agree the append is not viable. I verified
this locally against g++ 16.1.1 and clang 22.1.6 and then reverted it — the fix belongs on its own
branch, and a fix PR should also confirm the full suite and decide whether the now-reachable deleted
_append<sum> overload is still wanted.

Found while adding requires-clause coverage to tests/fn/pack.cpp (#85). tests/fn/pack.cpp
carries a GAP comment at the site rather than a negative probe, since the probe cannot be written
portably until this is fixed. Same class as #277 (validity decided outside the immediate context).

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingrelease-0.1Planned for release 0.1

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions