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), even while the build-time check’s"none"handling remains deferred. 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: unconstrained, a minimum standard level, or forbidden. Requirements accumulate along public dependency edges by taking the strictest (the join). A dependency edge is compatible when the consumer’s compile level, in every language the consumer compiles, satisfies the dependency’s accumulated requirement. A candidate package version is viable when every edge resolving to it is compatible. 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
ordered by strictness , defined case by case:
- for every ;
- for every ;
- iff in ;
- no other pairs are related, and is reflexive.
Informally: imposes nothing, requires a consumer level of at least , is unsatisfiable. In v1, is a finite chain (L1):
D4 (join). For , the join is the -maximum of and (well-defined because is total, L1). For a finite set or multiset , is the -maximum of , with .
Remark (reserved max and the interval generalization). Each interface requirement is
serialized as a pair {min, max} whose max slot is reserved and always absent in v1.
is designed so that populating max later is a domain swap, not a signature
change. Define the interval domain
where denotes , denotes , and denotes . embeds into by , , . On , the order is reverse set inclusion of the denoted sets, the join is set intersection of the denoted sets, and an empty intersection is (). Under the embedding, intersection of two up-sets and is , which agrees with the v1 join, and and remain identity and absorbing element. Every downstream definition in this document (D10 through D14) depends on only through the operations , , and the predicate / the set (D11, D12), all of which are defined on verbatim ( becomes membership of the consumer level in the denoted set). The extension therefore changes no downstream signatures. The rest of this document works in the v1 chain.
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
, where a level
means a declared minimum (
interface-c-standard/interface-cxx-standard), means the declared value"none"(headers not consumable from ), and means 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 | ||
| 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.
D11 (). For a consumer , a language , and a requirement :
D12 (satisfaction sets). For , define :
By construction, iff . We drop the subscript and write when is clear.
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 ( is a finite chain). is a finite totally ordered set with least element and greatest element .
Proof. Let (finite and totally ordered by D2; for C, for C++). Define by
is a bijection: the three shapes of D3 are disjoint, and is a bijection on the middle block. We check by cases on the definition of in D3:
- : holds for all , and holds for all .
- : holds for all , and holds for all .
- , : .
- , : D3 relates this pair only via reflexivity, which does not apply (), so ; and .
- , : similarly and .
So is an order isomorphism onto the integer interval with its usual total order. Total orders, reflexivity, antisymmetry, and transitivity transport along order isomorphisms, so is a finite chain; is its least and its greatest element.
L2 (bounded join-semilattice). is a bounded join-semilattice: is the least upper bound, and it is associative, commutative, and idempotent, with as identity and as absorbing element.
Proof. By L1 the order is total, so for any the -maximum exists and is one of the two elements. It is an upper bound of both by definition of maximum, and any upper bound satisfies because is above the larger of the two; so is the least upper bound. Through the isomorphism of L1, corresponds to on integers, which is associative, commutative, and idempotent; these equational properties transport along the bijection . For example , so
and injectivity of gives associativity; commutativity and idempotence are the same argument. is the least element (L1), so : identity. is the greatest element (L1), so : absorbing. Boundedness is L1’s least and greatest elements. Consequently the set join of D4 is well-defined for every finite multiset : by associativity and commutativity the result is independent of the order of combination, by idempotence it is independent of multiplicity, and is the identity, so for all finite .
L3 (semantic characterization). For all :
- (Soundness) .
- (Completeness, up to one degenerate pair) , except for the single pair , (where is the least level, D2).
- (Induced equivalence) iff or .
Consequently coincides with reverse -inclusion on the quotient of by the equivalence , and is an order isomorphism from that quotient (ordered by ) onto its image ordered by . The two identified elements are kept distinct in anyway: they are behaviorally equal for but differ in provenance (nothing declared versus a declared minimum), which diagnostics report.
Proof.
(1) Assume ; cases on D3. If then for any . If then . If , with : means , so . These three cases cover every related pair (D3 relates no others, and the reflexive pairs are trivial).
(2) We prove the contrapositive: assume and ; we show , i.e. exhibit . Since is total (L1), means strictly. Cases on the strict pairs, using the chain of L1:
- , with (the excluded pair is exactly ): take . Then , and because .
- , : any works ( is nonempty); and .
- , with : take . Then so , and so .
- , : take ; .
(There is no case with , since is greatest.) In every case , proving the contrapositive.
For the excluded pair itself: , so holds while fails (D3, as in L1’s fourth case) - the exception is genuine and is the only one.
(3) If , equality of is trivial; and was just shown. Conversely assume with ; by totality (L1) one is strictly below the other, say . Mutual inclusion holds, so by the contrapositive argument of (2) the pair must be the excluded one: . (Every other strict pair produced a separating .)
L4 (join is intersection of satisfaction sets). For all : . More generally, for finite : , with the convention that the empty intersection is .
Proof. By L1, is total, so without loss of generality , hence and by L3(1) . Then , the last step because is the smaller of two nested sets. The finite generalization follows by induction on : the base case is (the empty intersection), and the step is the binary case just proved together with (L2).
Remark. L4 is the v1 shadow of the interval rule of D4’s remark: under the interval generalization, becomes the denotation itself and L4 becomes the definition of join (intersection), with . Nothing downstream distinguishes the two readings.
L5 (antitonicity of ). If and , then .
Proof. iff (D12). By L3(1), , so , i.e. .
L6 (satisfaction sets are upward closed). For every , if and then . Consequently, raising a consumer’s effective level in any language never breaks satisfaction of any requirement: if and agrees with except , then .
Proof. Cases on (D12). is upward closed trivially. : from and , transitivity of gives . is upward closed vacuously. The consequence is immediate from D11/D12: iff , and stays in the upward closed set.
L7 (set joins are monotone). For finite multisets over : . Moreover, if and with pointwise, then .
Proof. For the first claim: is an upper bound of every element of , hence of every element of ; since is the least upper bound of (L2), . For the second: each , so is an upper bound of , and again leastness of gives .
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 smaller - 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 - again a relaxation moving down, and again the point of the declaration.
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 three tags, and the case is a single comparison of two 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, by (1)‘s comparison bound and L2’s ). 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 minimum , its public headers compile under every consumer level in
that language; 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(1), , 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: , , , . 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 (or triples, for associativity) and assert the property, citing the lemma number it checks (L1 totality/antisymmetry/transitivity, L2 associativity, commutativity, idempotence, identity and absorption, L3 soundness and its single exception, L4 intersection, L5 antitonicity, L6 upward closure). 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 upward closed (L6), and rows shrink as the requirement climbs the chain of L1 (L3(1) in table form).
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. (L6 view: is upward closed, sits inside it, sits 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(L6: once inside , raising further never breaks it), 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 went down the chain (): 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).
Exhaustiveness note
Every check above is a lookup in a table like the reference table, and both tables and graphs here are tiny by construction of the model: has at most 9 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-L6 by full enumeration of those tables (citing the lemma numbers), T1/T2 on small DAGs including the diamond of Example 2 and the chain of Example 3, and each row of D9 by a dedicated fixture - covering C alongside C++ throughout.