Shorter spellings for the public API, and examples that use them - #9
Merged
Merged
Conversation
added 30 commits
September 30, 2026 17:09
The examples repeat a handful of shapes that make the library read as
verbose: exact numbers spelled as Rational { n, d }, a value dug out of a
result through three accessors, a trace built by hand, printf noise, and a
rounding restated at every use. The design and the plan add short
spellings for each, keeping every existing one.
examples/simple.cpp now prints with std::println, the spelling every
example moves to.
Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
A decimal such as 27.3 written as a double is the binary fraction nearest it, not 273/10, and Rational deliberately refuses a double for that reason. That left Rational::from_decimal(273, -1) as the only exact way to write a decimal, which is hard to read in a formula. `27.3_r` reads the spelling itself, so nothing is rounded on the way in. The literal is consteval and takes the raw spelling: integers, a fraction with a leading or trailing point, an exponent that scales exactly, digit separators, and trailing fractional zeros that cost nothing. A spelling Rational cannot hold, or one that is not a decimal (hexadecimal, binary, and a leading zero, which C++ reads as octal), fails to compile and the diagnostic names the guard that was reached. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…series
A series of measurements had to spell every element as
Measured<Q> { Rational { ... } }, and a point that was not measured as
Measured<Q>::absent(), although the quantity is already stated once in
measured_series<Q>. Each element may now be a Measured<Q>, anything a
Rational is built from (127, 10.3_r, a Rational), or not_measured.
A wrong element draws one message: a Measured of another quantity, a
string or a bool is refused by measured_series in its own words, and a
double or a wide unsigned integer by Rational's, with the series' own
check silent.
band(low, high) and breakpoint(key) likewise take exact numbers, so
band(83.7_r, 97.3_r) and breakpoint(12.7_r) read as the numbers they are.
Every earlier spelling still works. The header comments that called a
rat() helper which does not exist now show the real spellings, and the
line numbers the documentation quotes from the headers follow the move.
Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
A hexadecimal spelling with an E in it, such as 0x1E, looks like it has an exponent, so the leading-zero check is skipped and only the digit check in the loop refuses the x. Nothing pinned that check: without it the x would be read as a digit and the literal would yield a wrong number silently. It now has a negative case and a comment saying why it must stay. Also pin the spellings a reader is most likely to get wrong: 00.5 and 0e3 are exact, 1e-18 is the largest exact denominator, and 1e19 is refused by name however it is spelled. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
A bool is not an exact number: Rational is deliberately not built from one, so `measured_series<Q>(127, true)` must be refused, and by one message rather than an overload list or a second refusal from the conversion that would follow. The sibling cases pin a Measured of another quantity, a double and a wide unsigned integer; a bool was the one wrong element left unpinned. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
Reading a number out of a result meant choosing between is_value(), measurement().value() and the std::expected around it, and forgetting one check turned an absent number, a verdict or an error into a throw or a zero. number_of returns a std::optional<Rational> for a Measured, an Outcome, a checked_evaluate result, an Evaluated<Rational>, a RetryOutcome and a RejectionOutcome: the number when there is one, nothing otherwise. Because comparing an empty optional with a number is false, number_of(checked_evaluate<Q>(...)) == 0.5_r is a complete check. Zero is not used for "no number", since zero is a measurement. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…e kinds and failure sites Five public enums had no words of their own, so every caller that wanted to print one wrote a switch: the two examples each carried a local describe(ConstraintOutcomeKind). The library now gives each a lowercase phrase with no trailing punctuation, as describe(ArithmeticError) does. The two local helpers are removed, since an unqualified call would now be ambiguous with the library's by argument-dependent lookup. Their words equal the library's, so the examples print exactly what they did. The CHANGELOG records the rule for a consumer's own describe of these enums. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…fault symbol_of's vocabulary The examples and a consumer's own output print results, units, dimensions and the words of an enumeration, and each of those needed a hand-written helper. std::format now writes an Outcome<Q> (a value as a Measured, an empty one as "(not measured)", a verdict or invalid one as its label), a Unit (its symbol), a Dimension (L^2 M^-3, L^(1/2), (dimensionless)) and every enumeration with a describe(), which lists itself beside that describe() so format.hpp includes no further header. symbol_of<Q>() without a vocabulary is the declared symbol, and render() and document() take RenderOptions without a vocabulary that renames nothing. Two hygiene checks learn the new spellings: the forwarding overloads that pass RenderOptions on, and format.hpp's use of <string>. The documented diagnostic quoted from format.hpp follows its line. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…ensions where it is written Callers who would only rethrow the error of checked_convert_to, checked_round_to_declared or checked_within_bounds on a Measured had no short spelling: convert_to, round_to_declared and within_bounds now throw ArithmeticException where the checked form returns an error. checked_convert_to also knew both dimensions where the call is written but only refused a mismatch at run time, as DomainError. It now refuses a conversion between quantities of different dimensions, with or without a value present, when the call is compiled, with one message. The tests that pinned the run-time error become negative compile tests. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
… one format.hpp's overview still described two formatters, so the rule to include it wherever a type is formatted, and the warning that a consumer's own std::formatter defines the same entity twice, did not reach Outcome, Unit, Dimension or the described enumerations. It now names all of them, the Unit, Dimension and enumeration formatters carry the ownership sentence, and the changelog records that a consumer's own formatter for these types collides with the library's. The vocabulary tripwire could not see the two spellings that resolve the default vocabulary without naming it, symbol_of<Q>() and a two-argument render or document call, so it refuses both. The enumeration formatter reads describe() through a helper inside formula::detail, so a consumer's global named describe cannot hide it. Tests pin all twelve enumeration rows and the absence of one for an enumeration with no describe(), the padding and default alignment of an outcome's label, a named base in a dimension, and the refusal of a bad spec for an outcome. The probe now covers document with options. The five headers that specialise formats_by_describe include error.hpp, and the new rows share the existing detail blocks in series.hpp and snap.hpp. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
… explain twin Only formulas, series and retries could be traced in one call; a method, a curve, a rejection, a constraint or a conformity check needed a hand-built Trace and RecordingSink around the verb, which every caller repeated. traced runs any evaluation that takes a sink with a RecordingSink and returns what it returned, failure included, beside the steps recorded. Seven twins (explain_method, explain_check_method, explain_curve, explain_rejection, explain_check, explain_check_all, explain_conformity) give each remaining verb the same one-call form, and explain_series and explain_retry are now written through traced with the same result types as before. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
A method that rounds the same way in several places repeated the same three arguments -- a unit, a number of places, a mode -- at every use. DecimalRounding and SignificantRounding name them once, and declared_rounding takes the places a unit already declares. Every public factory that took the three arguments separately gains an overload taking the named value and building the very same type: rounded, rounded_to_digits, rounding_rule, with_rounding (with and without a citation, the latter still refused), rounded_output, rounded_sqrt and rounded_elementwise. For rounded_elementwise the named places apply to every element of the series. Every earlier spelling stays, and a unit of the wrong dimension still draws the rounding node's one message. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…ary it is given Only explain_method was tested with a renamed quantity, so a twin that dropped the vocabulary from its traced call would have written the default symbols and passed. Each remaining twin now has a case comparing its rendered trace with a hand-built RecordingSink over the same vocabulary, and asserting that the renamed symbol appears there and not in the default one. Also require a verdict to be present before explain_check_all's test reads its label. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
The consumer-globals probe instantiated only rounded<R> and declared_rounding, so a parameter name in any other new overload could shadow a consumer's global unseen. It now instantiates each of them, and doing so found one: filling a value-initialised PlacesTable made cl instantiate a helper that warned about a hidden global `i`. The table for rounded_elementwise<R> is now built from an index pack. The single-value refusal of rounded_elementwise<R> gets its own negative case, so deleting it or splitting its message is noticed. with_rounding<R>() forwards to the three-argument refusal instead of repeating its text, declared_rounding reuses declared_decimals and says what a unit declaring decimals outside -18 to 18 does, and the tests for rounded_sqrt, rounded_output and with_rounding also check a second, different rounding so an overload that ignored its value would fail. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
Every verb that evaluates a formula is told its result quantity at every call, because the library never deduces one from an expression: a dimension does not name a quantity. So the quantity was repeated at every call site, where a call could name another quantity of the same dimension and nothing would notice. yields<Q>(expression) names the quantity once, where the formula is written. evaluate, checked_evaluate, checked_evaluate_series, checked_evaluate_rejection, explain, checked_explain, explain_series, explain_rejection and define take the bound formula and take Q from it; render and document write the formula it holds. Nothing is deduced from the expression. Q is still the author's, checked against the dimension the expression computes where it is bound, with checked_evaluate's own message, and a quantity named again at a call is accepted only when it is Q. A verb handed a refused bound formula asks nothing further, so one mistake draws one message. Where a verb's own check would add a second, differently worded one -- define and the rejection's evaluation and trace -- a negative case pins the gate. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
The project prints with std::print and std::println everywhere outside the CRT-failure handler, so the gallery generator, the census report and the census tests move off printf. Their output stays byte-identical. support/fail_without_dialogs.cpp keeps std::fputs, now with a comment saying why: its invalid-parameter handler is noexcept and must neither allocate nor throw, which std::print may do. The package consumer under test/package keeps printf: the Package workflow builds it on ubuntu-24.04 with the default g++ 13, whose standard library has no <print>. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
The gallery generator names the stderr macro, which <cstdio> defines, so it includes that header itself instead of relying on <print> to pull it in. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
traced() calls std::invoke and constrains on std::invocable, but trace.hpp included neither <functional> nor <concepts>. MSVC reached them through other headers; libstdc++ and libc++ do not, so g++ 14 and clang 22 failed with "invoke is not a member of std". std::invoke_result_t, std::remove_cvref_t and std::move were already covered by <type_traits> and <utility>. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
… package test The library supports only the newest GCC, the one the main build pins (g++ 14), so there is no reason to keep older GCC working. The Package workflow's Linux leg now installs g++-14 and builds both the library and the consumer with it, and the README and changelog say that older GCC is not supported. With that, the package consumer prints with std::println like every other program; its output is unchanged. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
A bound formula may be evaluated for the quantity it names and no other, and nine verbs each enforce that on their own. Only one of them was compiled by a negative case, so eight refusals could have been deleted without any test noticing. One case now asks each verb for a different wrong quantity, and counts one message per verb. Compiling those refused calls found a second message. Each verb gated on the value of the check that had just failed, and clang-cl then compiled both branches of define's gate, whose two returns deduce different types. Each verb now states the refusal, and gates on a plain predicate that no failed check can spoil. A formula bound twice was not refused, and the verbs forwarded it to the inner binding, whose answer is for another quantity: one mistake gave a cascade of conversion errors. It is now refused where it is written, even for the same quantity, since a bound formula is the top of a formula. Tests also observe that the sink and the vocabulary handed to a bound formula's verb reach the formula, and that define of a bound series draws the series refusal. The consumer-globals probe now calls every bound overload, and yields.hpp includes what it uses directly. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
A bound formula around a bound formula is refused where it is written, and every verb given it is meant to say nothing more. The series and rejection verbs only take a bound series or a bound rejection, so a nested one reached none of their overloads: the refusal was followed by "no matching function", two messages for one mistake. Each of the four -- checked_evaluate_series, explain_series, checked_evaluate_rejection and explain_rejection -- now takes the nested formula too, and answers it with a placeholder that is never seen, so the refusal is the only message. Two negative cases, one for a series and one for a rejection, hand the nesting to those verbs and count one message. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…spellings
The seven small examples now read the way the library is meant to be
written. A value is supplied as `Measured<Q> { 180 }` or `0.5_r`, a formula
evaluated more than once names its result once with `yields`, a result is
checked with `number_of`, and a conversion that cannot fail uses the
throwing twin. Every line prints with std::println, formatting the
library's own values: a Rational, a Measured with its unit, an Outcome, a
Unit and an enumeration's words.
Printing the values themselves changes some lines, each on purpose. The
water/cement ratio prints as 0.6 rather than 0.600000 and says whether it
was derived or manually entered, the circular area is marked as rounded, a
converted volume carries its unit, and an absent measurement reads
"(not measured)". exact_numbers gains the self-check it lacked, and its
test now pins its closing "all checks passed: yes" beside
"ten tenths == one: yes".
Every README snippet is now consecutive lines of an example, beside that
example's real output, which also ends the README calling evaluate<> where
citations.cpp calls checked_evaluate. The documentation site's front page
opens with the same two blocks and output. The guides quote the new lines,
name what their snippets use, and show the short spellings where they
documented the long ones. docs/numeric-headroom.md's census table is
regenerated from the rewritten examples.
Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…t spellings
dimensions_and_units now prints every dimension, number and bounds verdict
with std::println. The formatters in format.hpp for a Dimension, a Rational
and an enumeration replace its own print_dimension and its printf calls.
Its inputs are _r decimals. It rounds to the kilogram's declared precision
with the throwing round_to_declared, because nothing in it can fail. Every
line of its output is unchanged.
display now writes its values as 25.5_r and Measured<Q> { 144 }. It supplies
the dish's weighings through measured_series. It traces with explain and
explain_conformity, instead of assembling a Trace and a RecordingSink. It
renders and documents with RenderOptions alone, and prints with std::println.
A new sixth section formats an Outcome, a Unit, a Dimension and an
enumeration. The Outcome rows cover a value, an empty outcome and a verdict,
whose label is right-aligned. Each row is printed beside its call and
checked. The first five sections print exactly what they printed before.
docs/display.md quotes the new lines. Its new section, "Formatting outcomes,
units, dimensions and enumerations", says that format.hpp must be included
wherever these are formatted. Its table and output block are checked against
the program, like the rest of the page. docs/dimensions.md quotes the new
rounding call and says what formats a Dimension.
Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…truncating it
`breakpoint(1.5)` compiled and meant `breakpoint(1)`: a conversion from
double to std::int64_t beats the user-defined conversion to Rational, so
the integer overload took the call and dropped the fraction without a
word. `breakpoint(1.5, 2)` and the four-argument `band(12.7, 1, 17.3, 1)`
did the same. A key or bound that reads as an exact decimal and silently
becomes a different number is the one thing this library must not do.
Each form now has an exact-match overload for floating-point arguments
that refuses in the library's own words and names the spellings that work
(`12.7_r`, `Rational { 127, 10 }`, `breakpoint(127, 10)`). Integer calls
do not satisfy the constraint and still take the old overloads. The line
numbers the documentation quotes from band.hpp follow the move.
Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…amples A std::expected that holds an error must not be dereferenced, and an example is where a reader learns how to read one. The expressions example now checks each checked_evaluate before it reads the outcome: its three compile-time results with a static_assert, so an error stops the build, and the water/cement ratio with an if that prints the error's words and fails. The quantities example goes back to checked_convert_to, checked_round_to_declared and checked_within_bounds, and checks each the same way, rather than trading the check for the throwing spellings to be shorter. Nothing they print changes. The README and the guides quote the guarded lines, and the numbers guide says that std::format writes a Rational's exact decimal where it has one, and gives each block that uses _r its using-directive. The tracing guide names the fixtures its vocabulary snippet uses, and the census test says how the expressions example now writes the circular area it copies. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…ument The refusal of a floating-point key or bound was pinned only for the first argument of each form. The other positions are separate arms of the constraint, so dropping one of them would have let a double be truncated again without any test noticing. One case per remaining position is added, plus one with two floating-point arguments of different types, which must still draw a single message; a guard instantiated once per argument would draw two. The changelog entry now says the message names the exact spelling for the call that was made, which is true of both messages, and each refusing overload's comment says that only a floating-point type is refused. The line numbers the documentation quotes from band.hpp follow the comments. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…ns and display examples The examples handle errors the way the library means them to be handled. A std::expected is checked before it is read, and nothing swaps in a throwing spelling to be shorter. The display example now traces with checked_explain, and checks each of its three evaluations before it reads the outcome or the trace. On an error it prints the error's words and fails. The dimensions example rounds with checked_round_to_declared and bounds-checks with checked_within_bounds, and guards each result the same way. It no longer unwraps the bounds checks unchecked. The display example's outcome rows now name the checked result as moisture->outcome. The program prints each call as its source spells it, so those three reference lines change with it. Nothing else either program prints changes. The guides quote the guarded lines and say what the std::expected holds. The display guide says that its verdict outcome is built directly, as a rejection would yield it, and why some outputs are quoted. The dimensions example writes its million litres as 1'000'000_r. The README names the using-directive that its 0.5_r needs. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…ples in the short spellings
The three examples and the guides that quote them now read the way the
library is meant to be used: exact decimals with _r, inputs as
Measured<Q> { n }, bands as band(low, high), a rounding named once as a
DecimalRounding or SignificantRounding, a formula evaluated repeatedly
bound with yields<Q>, and values checked with number_of. The hand-built
trace in the constraints example is explain_check, and the lookup
example's trace keeps a miss's derivation through checked_explain. Every
checked result is tested before it is read, and everything prints with
std::println.
Values that used to print through to_double() or as a fraction now print
as the exact decimal the library formats (26 mm, 0.448, 1.051, 36.28052
MPa); the guides quote the new lines. The rounding guide gains a
SignificantRounding example and one declaration style per snippet, and the
README and lookup-table guide show the two-argument band().
Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
… short spellings
The three examples and the guides that quote them now read the way the
library is meant to be used: exact decimals with _r, inputs as
Measured<Q> { n }, series as measured_series<Q>(130, 210, 95), bands as
band(low, high), the method's rounding named once as a DecimalRounding,
and a formula evaluated repeatedly bound with yields<Q>. The hand-built
trace blocks are explain_method, explain_check_method, explain_series,
explain_curve and explain_conformity, or traced around a verb that has
no twin, and each evaluation is run once and read for both its value and
its derivation. The local rat, m<Q>, exact, trace and outcome_word
helpers are gone; constraint outcomes print with the library's own
formatting.
Every checked result is tested before it is read, and everything prints
with std::println. The programs print exactly what they printed before,
so the guides' quoted output is unchanged; only their quoted code moved.
Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…ings The constraints example's header comment lost its last line, and the rounding example carried its include block twice. The top-row band is now band(173_r, 211.1_r), like every other decimal bound, and the lookup guide says why its gap test spells the bounds as integer pairs. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
added 27 commits
September 30, 2026 21:50
…ords and methods examples Sites that only read a trace now call trace_of, trace_of<Q> or trace_of_si instead of wrapping a checked evaluation in traced. The gated reads in the records example use checked_explain<Strength>, which gives the same trace and keeps the outcome for the no-answer check. The series example runs its passing series once and renders it at both step budgets, tests presence with contains, and writes its one fraction as 27708_r / 425. The methods example says why it runs the north a second time. The three programs print exactly what they printed before. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…at compile time Where an example shows only a trace, it now asks for the trace alone. The statistics of a constant sample are checked with static_assert instead of a runtime guard, and a trace rendered twice is rendered once. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
deviation_in_stddevs takes a Node, so the bare 1.75_r in its documentation did not compile; wrap it in number(). Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…n integers in the statistics example The tracing guide quoted an older body of explain; point at trace.hpp instead. The double-trace sentence now says it is explain that records in Rational. sixMasses spells 44 and 40 as integers like its siblings. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…d and breakpoint negatives Add negatives for 1e1001_r and 0b101_r, a BelowMinimum check for within_bounds, and widen the vocabulary-reach tripwire so it also sees symbol_of<decltype(x)>(). The band and breakpoint negatives now return 0 and say plainly why their floating-point types differ. Explain in rational.hpp why hasPoint is also true for an exponent, and rewrap a long comment in fail_without_dialogs.cpp. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…at it should The documentation implied trace_of_si differs from trace_of<Q> by units, which it does not; the real difference is that trace_of<Q> is empty where a value was typed in for the result and trace_of_si traces the derivation anyway. Both docstrings and the guide now say so, narrow "a failure is the last step" to failures while the expression is evaluated (a failure converting into the result's unit comes after the trace), and use names the guide already has. trace_of now reaches checked_evaluate's series refusal instead of an overload list, and trace_of_si goes through the same dispatch as checked_evaluate so a consumer's own node at the root still traces. The nested-Yields gate, the verb lists that say "every" and the dimension negative's second-message guard are now pinned, each with a deletion check. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…cap itself, mark the refused lookup snippet The widened symbol_of pattern dropped angle brackets, so symbol_of<Wrapper<Q>>() slipped through; it now accepts them and one level of parentheses, and stops at a statement end so it cannot run on into later code. The cap negative now uses an exponent too large for an int, so it fails when the cap is deleted (the old 1e1001 case was also refused by a later guard); 1e1001_r stays as its own case. The lookup comment says its snippet is now refused. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
… a single-value verb A bound formula is meant to be refused in the library's words when it is misused, with one message. But the verbs that answer with one value -- evaluate, checked_evaluate, explain, checked_explain, trace_of and define -- forwarded whatever the Yields held to an overload that takes a single expression. A bound rejection reached none of them, nor did a bound series, retry or whole opaque call handed to explain, checked_explain, trace_of or define: the mistake was reported as "no matching function" inside the library, which reads as a library defect. Each of those verbs now refuses a bound formula that is not an expression of one value, and forwards only one that is, so the refusal is the only message. A series is refused as checked_evaluate already refuses one, a retry and an opaque call as checked_evaluate refuses them, and a rejection in new words that name checked_evaluate_rejection and explain_rejection. define keeps its own words for a series. trace_of_si refuses a series as trace_of does. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
A bound formula is the top of a formula, not a part of one; the formula it holds, .expression, is the operand. Written as an operand anyway -- var<WaterVolume> * ratio -- it matched no operator, and the mistake came back as a list of twenty to thirty candidate operators. Arithmetic with a bound formula on either side, and its negation, is now refused in one sentence that names .expression. The operators take part only when an operand is a bound formula, so arithmetic over formulas resolves exactly as before. As for a retry, each gives a node refused already, so an evaluation of the result asks nothing more, and names its return type, so a concept asking whether a bound formula can be added is answered without the refusal. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
The refusal of a rounding rule with no citation is shared by both spellings of with_rounding, yet named only the three-argument one; it now names with_rounding<...>(). The unit conversion's note no longer cites the compilers it was measured with, the every-verb negative counts ten verbs and says which REJECT is g++'s, a rewrapped paragraph in lookup.hpp is reflowed, and the vocabulary check says it misses a template argument split across lines. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
… root The guide's trace_of example now uses the names of the explain example it points back to. trace_of_si consults no entered value for a result, while an input typed in is read as any other, and a failure is the trace's last step only at a node that reports to its sink, as each of this library's does. A test traces a consumer's two-parameter node at the root of trace_of_si, which compiles only through detail::dispatch. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
The probe now instantiates explain_check, explain_check_all, explain_check_method, explain_curve and the unbound explain_rejection, so a consumer's global hidden by one of their locals breaks its build, and checks each against the outcome of its untraced verb. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
The entry named only describe, whose clash is loud. An unqualified call with library arguments now also finds number_of, convert_to, round_to_declared, within_bounds, traced, trace_of, trace_of_si and the explain twins; a consumer's template of the same shape is displaced silently, and a non-template or differently typed one is ambiguous. using namespace formula also brings _r into scope. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
The plan named a local status file, its board and the configuration that remembers the board's address, none of which is in the repository or means anything to its readers. Where a sentence carried a rule, it now says that the controller keeps local progress notes. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…ires Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…ails citations.cpp now says which error stopped the evaluation, and composition.cpp guards the evaluation outcome itself, printing its error, before reading the number from it. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…s read composition.cpp, rounding_and_conditionals.cpp and display.cpp no longer explain or checked_explain a formula just to take the trace out of it. composition.cpp also stops calling the throwing explain. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…verts _r stays where only a Rational is accepted, such as the first argument of MeasuredObservations and the operands that must stay Rational. The guides quote the same lines and follow them. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
An Outcome formats as the Measured it holds, with the same specs, so the .measurement() call in front of each print only spelled the same text longer. The output is unchanged, and docs/calculations.md quotes the same lines. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
… or the absence The four results in methods_and_overlays.cpp were each checked twice, for an error and then for no value. One guard on number_of now prints the error's words when there is an error and "no value" otherwise. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
… of a runtime guard records.cpp, quantities.cpp, statistics.cpp, dimensions_and_units.cpp and series.cpp evaluate, convert or bounds-check constants, so the std::expected is constexpr and static_assert(r.has_value()) checks it: an error stops the build. docs/quantities.md quotes the changed lines and follows them, and the census page's operation counts for the quantities example follow its source. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…rary's The entry said any non-template of the same name made the call ambiguous. That holds beside a non-template such as describe; beside one of the library's templates, a non-template taking exactly its parameter types is preferred and keeps working. The list of names now says it gives some of them, adds yields and declared_rounding, and says the example's describe was removed rather than renamed. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
The refusal of a rounding rule with no citation shipped in 0.2.0 opening "formula: with_rounding<U, Places, Mode>()", and a consumer's own negative test may match that opening. It now names with_rounding<...>() for either spelling, which is a change to tested text. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
A bound formula or a retry used as an operand is refused once, and the refusal gives a stand-in so that nothing over it asks again. render and document had no case for either stand-in, so each added a compiler's "no matching function" after the refusal. Each now renders as "(refused)" and documents nothing, as the other refused stand-ins do, and both operand negatives render and document the refused value and expect one error. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
A bound formula compared with a limit, as a constraint compares, drew the compiler's list of comparison operators none of which takes a Yields. It is now refused as a bound operand is, with the same message, and gives a comparison of refused values, so that a constraint, check, render or document over it adds nothing. The operators are constrained to a Yields operand, so comparisons of formulas are untouched, and a bound formula stays not equality-comparable. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…he whole-number spellings The census page named three examples that evaluate formulas at compile time; nine do now, and a row reading 0 | 0 | 0 is explained. The expressions guide says a comparison over a bound formula is refused in the same words as an operand. Two more whole numbers are written plainly in opaque_and_retry.cpp and dimensions_and_units.cpp, with the guide quote that follows the first. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…uantities does at run time The guide said a comparison over a bound formula "is refused in the same words" without quoting any refusal nearby, so a reader looking for the words found the one for a bound formula inside another, which is a different message. It now quotes the refusal both uses get. The census page said every check the quantities example makes is a compile-time one. One is not: it combines an absent input at run time. That computes no integer, which is why its row reads zero. Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Working with formula-cpp read as verbose. The examples show where that came from: a few shapes repeated hundreds of times.
formula::Rational { 273, 10 }, and five example files that define their ownrat()helper.Measured<Q> { Rational { n } }.r.has_value() && r->is_value() && r->measurement().value().Trace<>andRecordingSink, then the same formula evaluated again for its value.%.*s,.c_str(),static_cast<long long>and.to_double().This branch adds a short spelling for each. Every existing spelling keeps compiling.
27.3_r, an exact decimal literal. It is exact or it does not compile:0x1F_r,017_rand a valueRationalcannot hold are refused.measured_series<Q>(127, 10.3_r, not_measured), andband(83.7_r, 97.3_r)/breakpoint(12.7_r).number_of(x): the number a result holds, or nothing.describe()andstd::formatfor outcomes, units, dimensions and every described enumeration.symbol_of<Q>(), andrender(x, options)without a placeholder vocabulary.traced(…), and anexplain_*twin for every evaluation verb.trace_of<Q>(e, env),trace_of(bound, env)andtrace_of_si(e, env): the trace alone, whether the evaluation succeeds or fails.DecimalRounding,SignificantRoundinganddeclared_rounding, which name a rounding once.Measuredoperations.yields<Q>(expr): a formula bound to its result quantity, named once where the formula is written. Every verb that takes a result quantity takes it.Misusing one of these draws one
formula:message, not an overload list:bandorbreakpointargument, which used to be truncated silently;Every example, and every guide checked against the examples, is rewritten with these spellings. They print with
std::println, and they handle errors explicitly: everychecked_result is checked before use, either at run time or, for a constant, withstatic_assert. Measured over the 18 example programs, master against this branch:rat()helperRational { … }printfstd::println: 371)measurement().value()RecordingSink(hand-built traces).c_str()/%.*s/.to_double()None of the library's rules change:
Breaking changes. Two Changed entries in the CHANGELOG need attention on upgrade:
convert_to,within_bounds, …) is displaced silently, and a helper of another shape becomes ambiguous.checked_convert_toacross dimensions no longer compiles.GCC 14 is now the oldest supported GCC.
Verified on every compiler: cl and clang-cl in debug and release, g++-14, and clang-20 in debug, release and UBSan, 2087 tests each, refusal tests included. Doxygen 1.9.8 and
mkdocs build --strictalso pass, and so do all CI legs, including macOS AppleClang and install-and-consume.The design is in
docs/superpowers/specs/2026-09-30-concise-spellings-design.md.