Two specialized bodies of an archiver's writer both match one call and neither is narrower - what does the compiler do?
answer
- two claims, neither inside the other
- specificity is a partial order
- the overlap belongs to both bodies
- error lands at the call, not the declaration
- add a body claiming exactly the intersection
basics
~20 sIt rejects that call as ambiguous. Specificity is a partial order, so when each matching body claims arguments the other does not, no candidate is narrowest and there is nothing to select - the error lands at the call, not at either declaration.
solid answer
~50 sThe most-specific rule only works when one candidate's claim sits strictly inside the others'. Two family overrides can overlap without either containing the other - one claims every batch whatever the sink, the other claims every fast sink whatever the record - and an argument pair in the overlap matches both. Neither is more specific, so the call is rejected. Note where the error appears: both declarations are individually legal, and the complaint arrives at whichever call first lands in the overlap, possibly in someone else's code. There are three ways out: add a third body claiming exactly the intersection, which is narrower than both; narrow one of the two so the claims stop overlapping; or take the choice out of selection entirely and have the caller name a distinct routine. The rejection is a feature - the alternative is an arbitrary silent pick.
code
pseudocode · 13 linesfunction archive<T, S>(record, sink) // general body
specialize archive for <Batch<T>, S>(record, sink) // any batch, any sink
specialize archive for <T, FastSink>(record, sink) // any record, fast sink
archive(aBatchOfRows, aFastSink)
// both family bodies match this call:
// the first also claims batches to slow sinks
// the second also claims single records to the fast sink
// neither claim sits inside the other -> no narrowest -> call rejected
specialize archive for <Batch<T>, FastSink>(record, sink)
// the fix: one body claiming exactly the overlap, narrower than bothgo deeper
Recall that two hand-written bodies can both fit one call, and that the build then fails rather than guessing. You are not expected to reason about overlapping families yet.
Explain the mechanics: specificity is a partial order, two claims can overlap without either containing the other, and the unique-narrowest step then has no answer. Name the fix as a body claiming exactly the intersection.
Show you know where this bites - both declarations build fine and the failure appears at a call site in a third module. The operational lesson is to keep all overrides of one routine together so overlaps are visible when written.
The judgment is whether family-shaped overrides belong in code other teams extend at all, since every new override widens the chance of an overlap that fails in a consumer's build rather than yours.
## Where the overlap comes from A family override claims arguments by **shape**. Two such overrides written for different reasons can end up claiming overlapping sets without anyone intending it: - one author wrote a body for **any batch of records**, because batching changes how the header is framed; - another wrote a body for **any record going to a fast sink**, because that sink accepts a raw span. Each body is sensible alone. But an argument pair that is *both* a batch *and* bound for the fast sink matches both, and the two claims are **incomparable**: the first covers batches to slow sinks, which the second does not; the second covers single records to the fast sink, which the first does not. ## Specificity is a partial order, not a ranking The rule is: A outranks B when **every** argument A matches is also matched by B, and B matches something A does not. That defines a partial order, and partial orders have pairs with no winner. | Candidate | Claims | Comparable to the other? | |---|---|---| | general body | every argument pair | wider than both; loses to either | | any batch, any sink | batches only, all sinks | no - covers slow-sink batches the other misses | | any record, fast sink | fast sink only, all records | no - covers single records the other misses | So for an argument in the overlap the candidate set has three members, one is eliminated as widest, and the remaining two tie. There is no tie-break to apply, and inventing one - source order, argument position, "leftmost parameter wins" - would make the selected behaviour depend on something unrelated to the types. ## Where the error surfaces, and why that matters Both declarations compile. The overlap is only a problem once some call actually lands in it, so: 1. The two bodies can be added months apart, by different people, each seeing a green build. 2. The failure appears at the **instantiation** - the call that fixes the argument - which may be in a third module owned by neither author. 3. The message names candidates the reporting engineer has never read. This is the practical reason to keep every override for one generic routine in one place: an overlap is easy to see when the claims are listed together and invisible when they are scattered. ## The three ways out | Fix | What you write | When to choose it | |---|---|---| | Claim the intersection | a third body for "a batch bound for the fast sink" | the overlap has a genuinely correct behaviour, usually a combination of the two | | Narrow one claim | restrict one body so the sets stop touching | one of the two was over-claiming and nobody noticed | | Stop specializing | expose two differently named routines the caller picks | the two behaviours are not the same operation and should never have shared a name | The first is the usual answer, and note the direction: the new body must be **narrower than both**, not a wider catch-all. Adding a body that claims *more* leaves the original tie untouched and adds a third loser to the set. ## Why the rejection is the right behaviour Selection has no run-time component to observe, so a silent arbitrary pick would be almost undiagnosable: the program would simply take one of two implementations, and a harmless reordering or a new import could change which. Rejecting the call converts that into a build failure at the exact argument pair that is under-specified, and forces someone to state what should happen in the overlap. Languages differ in how far they take this. Some support family overrides and this partial ordering with exactly this error; some accept only fully-fixed overrides, so no two claims can ever overlap and the question does not arise; some have no separate-body mechanism at all and expect a single body with an internal branch instead. The reasoning transfers - it is the partial order that produces the tie, not any one design.
- Why is a build failure here better than the compiler picking one of the two?Because selection leaves no run-time trace. An arbitrary pick would mean the program silently ran one of two implementations, and an unrelated edit could flip it with no diagnostic anywhere. The rejection pins the problem to the exact argument pair nobody specified, at build time, while both candidates are still on screen.
- Does adding a body that claims a wider set resolve the ambiguity?No. A wider claim is eliminated first as the least specific candidate, so the original tie survives untouched. Only a claim that is a strict subset of both tied candidates - the intersection - creates a unique narrowest body. Direction matters: narrower fixes it, wider does not.
saying these in an interview costs you the question
- Claims the compiler silently picks the first matching body
- Says the most recently declared body always wins a tie
- Thinks parameter order breaks the tie between two claims
- Believes adding a wider catch-all body removes the ambiguity
- Treats the ambiguity error as a compiler defect to work around