Standard compatibility: formal specification
1. Status and scope
This document is the normative specification of resolver-level language-standard
compatibility filtering. Where implementation code and this document disagree, this document
wins. Existing type names in the code (for example those in
crates/cabin-core/src/language_standard.rs) are non-authoritative implementation detail; the
implementation must be brought into agreement with this document, not the other way around.
The document is self-contained: an implementer must be able to build the compatibility module from this document alone, and a reviewer must be able to check every proof here without external context. It contains no implementation code.
In scope:
- The per-language requirement domain, its order, and its join.
- How a dependency target’s declarations map to a requirement on consumers, including header-only inference and the cross-language defaults.
- How requirements propagate along public dependency edges.
- Edge compatibility and package-version viability, as used by the resolver to filter candidate versions.
- Proofs of the algebraic and computational properties the implementation and its tests rely on.
Out of scope (specified elsewhere, consumed here as resolved inputs):
- The manifest surface: field names, parsing, target-over-package precedence, workspace
inheritance, diagnostics, and the interface/implementation contradiction lint. See
docs/language-standards.md. This document consumes only the resolved, typed per-target values (D6, D7). - Compiler flag lowering and toolchain support validation.
- How the resolver enumerates candidate versions. This document defines only the viability predicate the resolver applies to them (D14).
- The post-resolution, build-time interface enforcement documented in
docs/language-standards.md. That check runs after a resolution is fixed and keeps its own documented contract; this specification governs which candidate versions the resolver may pick in the first place. The two layers deliberately differ in two defaults. At the resolver, a compiled target with no interface declaration imposes no constraint (D9 row 4) - filtering versions by the implementation-standard fallback would reject resolutions the build-time check is already positioned to diagnose precisely, so the fallback stays a build-time concern. And an explicit"none"is unsatisfiable here (D9 row 1) - the resolver ranks such a version last and selects it only when nothing better is in range, where the post-resolution enforcement then refuses it (preference-mode.md). The build-time check deliberately leaves"none"to that post-resolution layer, whose per-edgeignore-interface-standardoverride must be able to unblock exactly that class; the range bounds themselves (minimum and maximum) are enforced at both layers. Where the two documents appear to disagree, each governs its own layer; for resolver behavior, this document wins.
2. The model at a glance (informative)
Every dependency target induces, per consumer language, a requirement: the set of consumer levels it accepts - everything (unconstrained), everything from a minimum up, an inclusive bounded range, or nothing (forbidden). Requirements accumulate along public dependency edges by intersecting their accepted sets (the join); an empty intersection accepts nothing. A dependency edge is compatible when the consumer’s compile level, in every language the consumer compiles, lies inside the dependency’s accumulated set. A candidate package version is viable when every edge resolving to it is compatible. Two consequences shape everything downstream: requirements are only partially ordered by strictness (two ranges can be incomparable), and a composed requirement’s two bounds may come from different sources - no algorithm or diagnostic may assume one declaration explains a composed value. Everything below makes this precise and proves it well-behaved.
3. Definitions
Notation: denotes “absent” for partial attribute values; is the empty set; is set inclusion; is the level order of D2; is the requirement order of D3. Definitions are numbered D1, D2, …; lemmas L1, …; theorems T1, …; corollaries C1, … Proof ends are marked .
D1 (languages). The set of languages is .
D2 (levels). Each language has a finite, totally ordered set of ISO standard levels:
The order is chronological enumeration order, not numeric order ( in
; in ). There is no equivalence special case
anywhere in either chain; in particular strictly. We write levels
as c89, …, c23 and c++98, …, c++26, and write for the level set of
language (,
). We write for the least
element of (c89, c++98).
Remark (aliases are outside the model). c90 is a parser-level alias of c89, and c++03
of c++98. Aliases are normalized by the manifest parser before any value reaches this model;
no alias is an element of or , and nothing in this document
mentions them again.
D3 (requirement domain). For each language , the per-language requirement domain is the interval domain
with the denotation - the set of consumer levels a requirement accepts:
is the minimum-only shape (declared min with no max); the
bounded shape (declared min and max, inclusive on both ends; is a manifest
validation invariant - an empty declared range is rejected at parse). The strictness
preorder is reverse inclusion of denotations:
Write when both directions hold, i.e. . is reflexive and transitive; it is antisymmetric only on the quotient by (L1 lists the classes) and it is not total: two ranges can be incomparable - e.g. and contain neither one the other. No definition, algorithm, or diagnostic may assume two requirements are comparable.
D4 (join). For , the join is the requirement denoting the intersection of the accepted sets: . The denoted sets are intervals of a finite chain and intervals are closed under intersection, so such a requirement always exists; it is unique up to , and the normative structural rule picks one shape deterministically:
- if either operand is , the join is ;
- otherwise take the lower bound as the maximum of the operands’ lower bounds (absent when neither has one) and the upper bound as the minimum of the operands’ upper bounds (absent when neither has one);
- no bounds ; lower bound only ; both bounds with ; both bounds with - the empty intersection - .
For a finite set or multiset , is the iterated join, with (L2 shows the result is independent of iteration order and multiplicity). An empty intersection arising anywhere in a composition collapses the whole join to : no consumer level satisfies the combined requirements, and diagnostics must be able to name both contributing bounds.
Remark (why -equal shapes stay distinct). and
denote the same set today, and
denotes the same set as . The shapes are kept
distinct anyway, for two normative reasons. First, provenance: diagnostics report exactly the
declared bounds (“c++17 or newer” versus “c++17..c++26”), and “nothing declared” versus “a
declared minimum at the lowest level” are different facts about the manifest. Second, chain
extension: when a future revision appends a new level to , a minimum-only
requirement accepts it and a bounded one does not - the two shapes diverge, so serialized
metadata must preserve which one was declared. The structural join of D4 preserves shapes
accordingly: it emits a bounded result only when some operand contributed an upper bound.
D5 (targets, dependency graph, public reachability). Fix a finite set of targets and a set of directed dependency edges , where means target depends on target . Each edge is classified public or private; is the set of public edges. The graph is acyclic; acyclicity is guaranteed by resolution before this model applies (a dependency cycle is an error upstream of compatibility filtering).
The intended semantics of the classification, which T4 makes precise as a premise: across any edge , the consumer ‘s translation units may include ‘s public headers; the edge is public exactly when ‘s public headers are themselves part of ‘s public interface (re-exported), so that headers reachable through ‘s public edges are in turn reachable from ‘s consumers. A private edge exposes ‘s public headers to ‘s translation units but not to ‘s own public headers. How an edge’s classification is declared in manifests is outside this document’s scope.
Define the public reachability set of :
is finite (it is a subset of ).
D6 (dependency target attributes). Each target carries the following resolved
attributes, produced by the manifest layer (precedence and inheritance already applied; see
docs/language-standards.md):
- - whether the target has translation units of its own.
- For each , an effective implementation standard
, with meaning the target does
not implement . Population contract: is non- exactly when
the target itself implements - a compiled target implements when it has sources of
(the level then resolves through the usual target-over-package precedence), and a
header-only target implements only through a target-level implementation declaration. A
package-level implementation default alone never populates , mirroring
the relevance rule of
docs/language-standards.md: an inherited implementation default says how sibling targets compile, not that this target’s headers involve . - For each , an explicit interface declaration
, one of: a declared range with
and ,
(
interface-c-standard/interface-cxx-standard; the string form"c++17"is the minimum-only , the table form{ min, max }a bounded ); , the declared value"none"(headers not consumable from ); or , no explicit interface declaration for .
Remark (why D6’s population contract matters). D9 routes on whether is
present. If a package-level implementation default could populate it, a pure-C++ compiled
target inheriting a package c-standard would take D9 row 4 ()
instead of row 6 () for C consumers, silently defeating the strict
C++-to-C default; and a header-only target inheriting a package cxx-standard while declaring
only interface-c-standard would manufacture a C++ interface minimum (row 3) for a language
its package never exposed. The population contract rules both out.
D7 (consumer effective standards). A consumer target compiles a (possibly empty) set of languages , and for each has an effective compile level . (A target that compiles a language without an effective standard is a manifest error upstream of this model; is total on .) A header-only target has no translation units, so as a consumer it has ; D13 spells out what its edges mean.
D8 / Invariant I1 (gnu-extensions is excluded). Each target has a boolean
gnu-extensions attribute, default false, which selects the GNU spelling of the same ISO
level at compiler-flag lowering time. Invariant I1: no definition, function, or predicate
in this specification takes gnu-extensions as an input; it never participates in
compatibility. In particular , , , edge compatibility,
and viability are independent of every target’s gnu-extensions value. This is a design
invariant: future revisions of this specification must preserve it. (GNU dialect strings such
as gnu++20 do not exist in manifests and are not levels; see D2.)
D9 (declaration-to-requirement function ). For a dependency target and a consumer language , define by the first matching row:
| # | Condition | |
|---|---|---|
| 1 | ||
| 2 | when , else | |
| 3 | , , | |
| 4 | , , | |
| 5 | , , | |
| 6 | , , |
The rows are mutually exclusive and exhaustive, so is a total function. Row by row:
- Rows 1-2: an explicit declaration always wins, over inference and over both
cross-language defaults. Declaring
interface-c-standardon a C++ target is exactly how its headers become consumable from C (overriding row 6), and"none"is how a C target opts out of C++ consumption (overriding row 5). - Row 3: header-only inference - a header-only target without an explicit interface declaration for infers its interface minimum from its implementation standard for .
- Row 4: a compiled target without an interface declaration imposes no constraint.
- Row 5: the permissive C-to-C++ default - a target that implements no C++ (in practice, a C target) is consumable from C++ at any C++ level by default.
- Row 6: the strict C++-to-C default - a target that implements no C (in practice, a C++
target) is not consumable from C unless
interface-c-standardis explicitly declared.
Remark (why the defaults are asymmetric). C headers are conventionally consumable from C++
(possibly via extern "C" guards, which are the author’s obligation under Assumption A in T4);
C++ headers are in general not valid C. The defaults encode that convention; both are
overridable per rows 1-2.
D10 (effective requirement ). For each language , the effective requirement is defined by the recursion
where the inner join is over the public dependencies of (and is when has none, by the empty-join convention of D4). Requirements propagate along public edges only; private edges of do not contribute to . T1 proves this recursion has exactly one solution on the finite DAG and gives its closed form.
Remark (per-bound provenance). Because the join intersects ranges, the lower and upper bound of may be attained by different elements of , and a may arise either from a single contribution (rows 1 and 6 of D9) or from an empty intersection of two bounds. An implementation that explains to users must therefore track provenance per bound - one origin chain for the lower bound, one for the upper - and, for an empty intersection, report both clashing chains; a single “origin of the requirement” does not exist in general.
D11 (). For a consumer , a language , and a requirement :
unfolded per shape: true for ; for ; for ; false for .
D12 (satisfaction sets). For , the satisfaction set is the denotation: . By construction, iff . We drop the subscript and write when is clear. (D11/D12 keep both names so the edge-compatibility prose reads the same as before; they are one function.)
D13 (edge compatibility). A dependency edge is compatible iff
The conjunction ranges over every language the consumer compiles: a mixed-language consumer must satisfy the dependency’s effective requirement for each of its languages. Languages the consumer does not compile impose nothing (in particular, for a language does not affect the edge). Compatibility is defined per edge; the edge’s own public/private classification does not appear in the condition (both kinds expose ‘s public headers to ‘s translation units, per D5).
A header-only consumer compiles no language (, D7), so every edge out of it is compatible vacuously - the empty conjunction is true. This is deliberate, not a hole: the target has no translation units for a requirement to constrain, and its dependencies reach the targets that do compile through propagation instead - when the header-only target’s edge to the dependency is public, D10 folds into the header-only target’s own effective requirement, and every downstream compiling consumer picks it up across its edge onto the header-only target (Example 3’s chain shows the same mechanism).
D14 (package-version viability). In a candidate resolution, a package version is
viable iff every dependency edge whose dependency target belongs to
is compatible. Equivalently: is excluded as soon as at least one edge resolving to it is
incompatible. Viability is the predicate that governs candidate preference: the resolver applies
it as a version-selection ordering (preference-mode.md), never as a hard in-solver filter, and
the post-resolution build-time enforcement of docs/language-standards.md is what actually refuses
an unviable resolution. How candidates are enumerated is outside this document’s scope; what
happens when no candidate is viable is answered by preference mode with select-latest-and-report.
4. Lemmas
L1 (structure of the domain). is a finite preorder with least element and greatest element . Its quotient by is a finite partial order in bijection with the set of interval-shaped subsets of (including itself and ), ordered by reverse inclusion. The classes are exactly:
- (all denoting );
- for each ;
- the singleton for each ;
- the singleton .
is not total: for disjoint or partially overlapping ranges - e.g. and - neither denotation contains the other, so neither nor .
Proof. Reflexivity and transitivity of are those of ; the bounds follow from . The map is surjective onto the nonempty intervals, , and by construction of D3, and it identifies exactly the listed classes (two shapes denote the same set iff they have the same lower endpoint and both reach the top, or are both empty). Non-totality is the displayed counterexample.
L2 (bounded join-semilattice). The structural join of D4 is associative, commutative, and idempotent on shapes (not merely up to ), with as identity and as absorbing element. Consequently the set join of D4 is well-defined for every finite multiset - independent of iteration order and multiplicity - with and .
Proof. Represent every non- shape by its bound pair , reading as “no bound”: , , . The structural rule combines pairs componentwise - on lower bounds and on upper bounds, each with as identity - and both components are commutative idempotent monoids, so the pair combination is associative, commutative, and idempotent, with as identity. The final rendering (collapsing an inverted pair to ) does not disturb this: an inverted pair arises in some grouping iff the overall intersection is empty (the componentwise bounds are grouping-independent), and absorbs every further join, so all groupings agree on the shape. Well-definedness of and the flattening law follow as usual from associativity, commutativity, idempotence, and the identity.
L3 (strictness is denotational). iff - definitional after D3/D12, recorded as a lemma because downstream proofs cite it. The induced equivalence is the of D3, whose classes L1 lists; -equal shapes are behaviorally identical for and differ only in provenance and under future chain extension (the remark after D4).
L4 (join is intersection of satisfaction sets). For all : , and for finite : , with the empty intersection denoting .
Proof. The binary claim is D4’s defining property; what needs proof is that the structural rule realizes it. Membership in means satisfying every lower bound and every upper bound present among the operands, i.e. the maximum of the lower bounds and the minimum of the upper bounds (each vacuous when absent) - exactly the set the structural result denotes, including the empty case rendered and the no-bound cases rendered / . The finite generalization follows by induction on using L2.
L5 (antitonicity of ). If and , then .
Proof. by L3.
L6 (satisfaction sets are convex, not upward closed in general). Every is an order-convex subset of : if with , then . Upward closure - “raising a consumer’s level never breaks satisfaction” - fails exactly for the bounded shapes with : raising past breaks satisfaction. Every other shape’s denotation is upward closed: itself, the up-sets of the minimum-only shapes, the empty set (vacuously), and a bounded range whose - though the last stays upward closed only for today’s chain, since a future appended level falls outside it (the remark after D4).
Proof. Each denotation of D3 is , an up-set, an interval, or empty; all are convex. For the failure claim: and any is not, and such exists exactly when ; the remaining shapes’ denotations are upward closed by inspection.
Remark (normative consequence for remedies). Diagnostics and documentation must not unconditionally advise raising a consumer’s standard. Below a minimum, raising (up to any cap) helps; above a maximum, only lowering the consumer, or changing the dependency, can - and against nothing at the standard level helps.
L7 (set joins are monotone). For finite multisets over : . Moreover, if and with pointwise, then .
Proof. By L4, for the subset claim (intersecting more sets can only shrink the result), and for the pointwise claim ( componentwise by L3). Both are by L3.
5. Theorems
T1 ( is well-defined on finite DAGs). On the finite DAG , the recursion of D10 has exactly one solution, namely the closed form
and it is computable by processing targets in any topological order of (dependencies before dependents), with the same result for every such order. Computation terminates.
Proof.
Well-foundedness. Since is finite and acyclic (D5), define as the length of the longest path from using edges in (finite: paths in a finite DAG cannot repeat vertices, so their length is bounded by ). If then (any path from extends to a longer one from ).
Existence and uniqueness. We show by strong induction on that any function satisfying the recursion of D10 must agree with the closed form at ; since the closed form itself is a well-defined function (each is a finite set and of a finite set is well-defined by L2), and substituting it into the recursion succeeds (verified below), existence and uniqueness both follow.
Fix and assume the claim for all with ; in particular for every public dependency of . Then for any solution :
The third equality is the flattening law iterated over the finitely many public dependencies, valid by associativity, commutativity, and the identity element (L2). The fourth holds because
by definition of (D5): a nonempty public path from starts with some public edge and continues as a possibly-empty public path from . Note that a target reachable through several public dependencies of (a diamond) contributes once on the right but possibly several times in the flattened multiset on the left; idempotence (L2) makes the multiset join equal to the set join, so the equality holds regardless of path multiplicity. Reading the chain of equalities backwards also shows the closed form is a solution of the recursion, completing existence.
Order-independence and termination. A topological order of the finite DAG exists and every prefix of the computation only reads values of targets already processed (each with precedes ). Whatever topological order is chosen, the computed value at each satisfies the recursion, and by uniqueness it equals the closed form - so all orders agree (confluence). Termination is immediate: steps, each a finite join.
T2 (growth). Let two attribute assignments over the same target set be given, with public edge sets and requirement functions satisfying for every . Write and for the respective effective requirements. Then for every :
Proof. implies for every (every public path in the smaller graph is one in the larger). Using the closed form (T1) twice:
C1 (adding a public dependency never lowers ). Adding an edge to (leaving every unchanged, and preserving acyclicity) satisfies T2’s hypotheses with equality on , so for every .
C2 (adding a declaration where nothing was imposed never lowers ). Suppose target has - by D9 that is a compiled target with no interface declaration for a language it implements (row 4), or a target consumed from C++ under the permissive default (row 5). Changing ‘s declarations so that for any , leaving everything else fixed, satisfies T2’s hypotheses: is the least element (L1), so , and every other target’s is unchanged. Hence for every .
C3 (viable versions can only shrink). Under the hypotheses of T2 (in particular after any change covered by C1 or C2), every dependency edge compatible under the primed assignment is compatible under the unprimed one; consequently every package version viable under the primed assignment is viable under the unprimed one - the set of viable versions can only shrink as public edges are added or requirements grow.
Proof. Let edge be compatible under the primed assignment: for every , . By T2, , so by antitonicity (L5) for every such : the edge is compatible unprimed. Viability of a version is a conjunction of edge compatibilities (D14); each conjunct transfers, so viability transfers. Contrapositively, growing the requirements can only remove versions from the viable set, never add any.
Remark (the two deliberate exceptions). C2’s hypothesis - that the prior requirement was - is essential, and two rows of D9 sit above the bottom by design:
- A header-only target with no declaration already imposes its inferred implementation
minimum (row 3). Declaring an explicit, older
interface-*-standardreplaces by a wider range - a relaxation that can move down. That is the declared purpose of the field: promising less than the implementation uses. - A target consumed from C under the strict default already imposes
(row 6). Declaring
interface-c-standardreplaces by a range - again a relaxation moving down, and again the point of the declaration. - Symmetrically, a change that only tightens - raising a minimum, lowering or adding a maximum, shifting a range so that it excludes previously accepted levels - satisfies T2’s hypotheses in the tightening direction and can only shrink the viable set (C3). A sideways shift both relaxes and tightens; neither T2 direction applies to it as a whole, and the viable set can change arbitrarily.
These moves are relaxations by the author of the dependency, widening its consumer set; T2 and C3 are about changes that tighten requirements. Both directions are monotone: T2 applied with the roles of the two assignments swapped shows a pointwise relaxation can only move every down and can only grow the viable set.
T3 (decidability and complexity). All predicates of this specification are decidable, with:
- in ;
- for all in per language, hence total (with a constant);
- viability of all package versions in a candidate resolution in overall.
Proof.
(1) A requirement is one of four shapes, and the range cases are at most two comparisons of elements of a fixed finite chain (D2): constant work.
(2) Fix . Compute a topological order of in (standard for finite DAGs). Process targets in reverse dependency order; at target , fold over (constant work: D9 is a six-row decision table over already-resolved attributes) and the stored values of its public dependencies (one join per outgoing public edge: D4’s structural rule is a constant number of comparisons). Each edge is touched once, each target once: . Correctness: this is exactly the topological computation of T1, which proved it yields the unique solution regardless of the order chosen. Summing over the two languages gives .
(3) With all values stored, checking one edge is a conjunction over , so at most two checks. Viability of every version is the conjunction, over each version’s incoming edges, of those edge checks (D14); every edge belongs to exactly one dependency target hence to one version’s conjunction, so all versions together cost . Adding (2)‘s precomputation gives . Decidability is immediate: every domain in sight is finite and every function is total (D9’s table is exhaustive; D11 is a three-case match).
Assumption A (author obligation). For every target and language : every consumer
level can compile ‘s public headers as
language at level . Unfolding D12, that means: if declares (or, per D9, infers)
an interface range, its public headers compile under every consumer level inside that range -
including, for a bounded range, no newer than its maximum, which is exactly how an author
records headers that use features a later standard removed; if , they compile under
every level of (for the C-to-C++ default, row 5 of D9, this is the C author’s
obligation that the headers are consumable from any C++ level, for example via extern "C"
guards); if , and the
obligation is vacuous. Assumption A is the package author’s obligation, not something Cabin
verifies (see Non-goals).
T4 (conditional semantic soundness). Let be a compatible edge (D13), and suppose Assumption A holds for every target and every language . Then for every such and :
and therefore, under A, every public header of every target in compiles as language at ‘s level . Since by D5 the public headers reachable from ‘s translation units through the edge are exactly the public headers of targets in , edge compatibility implies that ‘s translation units can compile every public include they can reach through .
Proof. Fix and . By the closed form (T1), , and a join is an upper bound of each of its elements (L2), so
Compatibility of the edge gives , i.e. (D12). By L3, , hence . Assumption A for and states that every level in compiles ‘s public headers as ; applying it at yields the conclusion for . As and were arbitrary, the claim holds for all of and all of .
Remark (scope of the guarantee). T4 speaks only about headers reachable along public edges below . Headers of ‘s private dependencies are, by the edge semantics of D5, not included from ‘s public headers, so ‘s translation units never see them through this edge and no constraint is needed; the private dependency’s own edge from is checked separately (D13 applies to every edge). T4 is exactly as strong as Assumption A: Cabin checks the arithmetic, the author promises the headers (see Non-goals).
6. Non-goals
This specification makes no claim about any of the following, and no lemma or theorem above should be read as implying one:
- ODR consistency across
#if __cplusplus(or__STDC_VERSION__) branches. Two translation units at different levels may see different definitions of the same entity through the same header; T4 guarantees each unit compiles, not that their definitions are link-compatible or ODR-consistent. - ABI and mangling. No guarantee that objects compiled at different levels link correctly
or mean the same thing at the boundary - for example, C++17 made
noexceptpart of the function type, changing template results and mangling relative to C++14 for the same header. - C++20 module BMI compatibility. Built module interfaces are compiler-, version-, flag-, and level-sensitive; nothing here models them.
- Verification of Assumption A itself. Cabin does not compile-check a dependency’s headers at each level of ; A is the package author’s obligation, and a violated A voids T4’s conclusion for the offending header without affecting any other result in this document (T1-T3 and L1-L7 are purely order-theoretic and hold regardless).
Appendix: worked examples
All domains in this specification are finite: , , , (two sentinels, the minimum-only shapes, and one bounded shape per pair ). Every per-pair claim below - and every lemma about , , , and - is therefore verifiable by exhaustive enumeration over the full domain, and the implementation’s test suite is expected to do exactly that: enumerate all pairs (and triples, for associativity, at least on the C chain) and assert the property, citing the lemma it checks (L2 associativity, commutativity, idempotence, identity and absorption; L4 intersection including the empty-intersection collapse; L5 antitonicity; L6 convexity and the failure of upward closure on bounded shapes; L1 non-totality by counterexample). The examples pick representative points of that space and work them end to end.
Reference table - over all of for the requirements used below (rows are requirements , columns consumer levels ; means , i.e. is true per D11/D12, and an empty cell means false):
| Requirement / level | c++98 | c++11 | c++14 | c++17 | c++20 | c++23 | c++26 |
|---|---|---|---|---|---|---|---|
Each row is an order-convex block (L6); the bounded row is the one that is not upward closed - it ends at its cap, while the and minimum-only rows are up-sets and the row is upward closed vacuously. : the two rows share no column (D4’s empty-intersection collapse).
Example 1: C++23 implementation, c++17 interface, consumed from c++17
Library : ,
,
(interface-cxx-standard = "c++17":
the public headers only need C++17 even though the implementation compiles as C++23). has
no public dependencies. Consumer : ,
.
- by D9 row 2 - the explicit declaration wins; the implementation standard never enters (contrast Example 5, where it would infer only for a header-only target; for this compiled target an absent declaration would give by row 4).
- (D10, D4, L2 identity).
- Edge : iff : true - see the row of the reference table. The edge is compatible (D13); if it is the only edge resolving to ‘s version, that version is viable (D14).
Example 2: diamond - consumers at c++17 and c++23 sharing one dependency
Targets () and () both depend on library (, , no public dependencies), and some root depends on both and - a diamond with shared at the bottom, both edges resolving to the same candidate version of .
- as in Example 1.
- Edge : - compatible.
- Edge : is false ( in D2’s chain) - incompatible.
- Viability (D14) is a conjunction over every edge resolving to : the edge cannot rescue ; because is incompatible, is not viable, and the resolver must find a version of whose requirement satisfies (or fail). One incompatible consumer poisons the version for the whole graph - exactly the per-edge conjunction of D13/D14. ( view: sits inside , below it.)
Example 3: "none" on a transitive public dependency poisons the root
Chain , both edges public; every target compiles only C++.
has - the
newest level there is. is a compiled library with no interface declaration; declares
interface-cxx-standard = "none".
- (D9 row 1). .
- (D9 row 4). By D10: - the absorbing element of L2 in action: once enters a join, nothing recovers.
- Edge : is false (D11) - incompatible at every consumer level, even (the row of the reference table is empty; ). Any version of that publicly depends on this is unviable for any C++ consumer: ‘s opt-out propagates up the public chain and poisons the root. Had the edge been private, D10 would not have folded into at all, , and the root would be unaffected - propagation is along public edges only.
Example 4: mixed-language consumer
Consumer compiles both languages: ,
,
. Dependency is a compiled C library:
,
,
(interface-c-standard = "c17"),
, no public dependencies.
- (D9 row 2).
- (D9 row 5: no C++ implementation, no declaration - the permissive C-to-C++ default).
- Edge is a conjunction over (D13):
- : is true.
- : iff : false (, D2 - no equivalence special case).
- One failed conjunct suffices: the edge is incompatible, even though the C++ side is
satisfied. must raise its C level to
c17orc23(a minimum-only requirement is upward closed, L6), or must relax its interface. Conversely, a C++-only consumer () would take only the first conjunct and pass: languages the consumer does not compile impose nothing.
For the strict opposite direction: if instead depended on a compiled C++ library with
and , then
(D9 row 6) and the conjunct would
fail at every C level - a C++ library is consumable from C only via an explicit
interface-c-standard (D9 row 2 overriding row 6).
Example 5: header-only inference
Header-only library : , (declared on the target itself - per D6’s population contract, a package-level implementation default alone would leave ), , no public dependencies. Consumer at .
- by D9 row 3: with no translation units of its own, ‘s headers are the implementation, so the implementation standard is inferred as the interface minimum. .
- Edge : is false - incompatible (the row of the reference table).
- Now the author audits the headers, finds they only use C++17, and declares
interface-cxx-standard = "c++17": , and D9 row 2 preempts row 3 - the explicit declaration wins over inference. , and the edge is compatible. Note this move widened the accepted set (): it is the first deliberate exception in the remark after C3 - a relaxation by the dependency’s author, widening the consumer set (T2 with the assignments swapped: the viable set can only grow).
Example 6: a bounded interface and the empty intersection
Library ships headers that use a construct a later standard removed (say, dynamic
exception specifications or register, both removed in C++17): its author declares
interface-cxx-standard = { min = "c++11", max = "c++14" }, so
and
(D9 row 2).
- Consumer at : is false - , the bounded row of the reference table. Raising cannot help (L6’s remark); only lowering to the range, or a newer , can.
- Aggregator publicly depends on both and a modern library with . By D10, - the empty intersection: no C++ level satisfies both. Every edge onto is incompatible at every consumer level, and a useful diagnostic must name both chains - the bound via and the cap via - because neither source alone explains the composed (the remark after D10).
Exhaustiveness note
Every check above is a lookup in a table like the reference table, and both tables and graphs here are small by construction of the model: has at most 37 elements, at most cells per language, at most cells, and D9 is a six-row decision table over finitely many attribute combinations. The implementation’s test suite is expected to verify L1-L7 by full enumeration of those tables (citing the lemmas), T1/T2 on small DAGs including the diamond of Example 2 and the chain of Example 3, the empty intersection of Example 6 with both provenance chains, and each row of D9 by a dedicated fixture - covering C alongside C++ throughout.