11. Methods and overlays
The strength of a cube and the strength of a cylinder are calculated by two different formulas, and which one applies depends on the specimen, not on a number. This chapter declares one method with a variant for each shape, and then changes how the method rounds for one jurisdiction with an overlay. Each trace says which variant ran and whose rounding rule applied.
The program is self-contained: it declares its own quantities and does not build on the previous chapters' programs.
One method, several variants
A variant applies to a kind of specimen, and the kind is named by a tag: an empty struct, never instantiated:
struct Cube
{
};
struct Cylinder
{
};
The cube is measured by the two sides of its loaded face, the cylinder by its diameter:
using Load = formula::Quantity<struct LoadTag, "F", "maximum load", unit::Kilonewton>;
using SideA = formula::Quantity<struct SideATag, "a", "first side of the loaded face", unit::Millimetre>;
using SideB = formula::Quantity<struct SideBTag, "b", "second side of the loaded face", unit::Millimetre>;
using Diameter = formula::Quantity<struct DiameterTag, "d", "diameter of the cylinder", unit::Millimetre>;
formula::method(variants, rounding rule, constraints) builds a method from
its three parts, in that order:
constexpr formula::DecimalRounding tenthMpa { unit::Megapascal,
formula::DecimalPlaces { 1 },
formula::RoundingMode::HalfAwayFromZero };
inline constexpr auto compressiveStrength = formula::method(
formula::variants(
formula::variant<Cube>(var<Load> / (var<SideA> * var<SideB>)),
formula::variant<Cylinder>(var<Load> / (formula::pi * formula::pow<2>(var<Diameter>) / formula::number(4)))),
formula::rounding_rule<tenthMpa>(),
formula::constraints());
formula::variants(...)lists the variants, each writtenformula::variant<Tag>(expression). The cube's strength is the load overa * b, the cylinder's the load overpi * d^2 / 4.formula::piis the library's fixed fraction for pi, 245850922/78256779, within 8e-17 of it; the trace shows which number was used. Every variant must measure the same dimension, here a stress.formula::rounding_rule<tenthMpa>()rounds whichever variant ran, to one decimal place of a megapascal, ties away from zero.formula::constraints()is the list of checks a result must pass; this method has none.
formula::explain_method<Tag>(method, environment) runs the variant Tag
names, rounds the result by the method's rule, and records each step. Its
outcome is a std::expected that holds an error or a std::optional,
empty when a measurement the variant reads is missing; a value is in the
coherent SI unit, here pascals. Its trace is the trace:
/// Evaluates @p method for the variant @p Tag names, and prints @p heading and
/// the trace. False when the method fails or gives no value.
template <typename Tag, typename M, typename Env>
bool show(char const* heading, M const& method, Env const& specimen)
{
auto const run = formula::explain_method<Tag>(method, specimen);
if (!run.outcome)
{
std::println("{}: {}", heading, run.outcome.error());
return false;
}
if (!run.outcome->has_value())
{
std::println("{}: no value", heading);
return false;
}
std::println("{}:", heading);
std::print("{}", formula::render_trace(run.trace, { .maxSteps = 20 }));
return true;
}
The tag is always stated by the caller, who knows what the specimen is. A tag the method has no variant for does not compile.
The specimens
A cube of 150 mm by 150 mm carried 675 kN, and a cylinder of 150 mm diameter carried 540 kN:
auto const cube = formula::environment(
formula::Measured<Load> { 675 }, formula::Measured<SideA> { 150 }, formula::Measured<SideB> { 150 });
auto const cylinder = formula::environment(formula::Measured<Load> { 540 }, formula::Measured<Diameter> { 150 });
Each environment holds only what its variant reads.
if (!show<Cube>("cube, base method", compressiveStrength, cube))
return 1;
std::println("");
if (!show<Cylinder>("cylinder, base method", compressiveStrength, cylinder))
return 1;
The cube's strength is 675 kN over 22500 mm², exactly 30 MPa. The cylinder's
loaded area is pi times 22500 mm² over 4, about 17671.5 mm², and its strength
540 kN over that area, about 30.56 MPa, which the method's rule rounds to
30.6 MPa; the trace writes it as the exact fraction 153/5 MPa. The last two
steps of each trace say whose rule
rounded the value, (method default), and which variant ran, variant Cube
(1st of 2), selected by tag.
An overlay changes a method for a jurisdiction
A jurisdiction's annex may change a method: fix a constant, replace a
variant's formula, drop a variant, or round differently. An overlay lists
those changes, each with the citation of the clause that makes it, and
formula::apply(overlay, method) returns a new method with the changes made.
The base method stays as it is.
The invented "Example jurisdiction" rounds the strength to whole megapascals. A rounding rule keeps a number of decimal places of a unit, so a coarser step such as 0.5 MPa cannot be stated as one; whole megapascals is the nearest coarser rule.
constexpr formula::DecimalRounding wholeMpa { unit::Megapascal,
formula::DecimalPlaces { 0 },
formula::RoundingMode::HalfAwayFromZero };
inline constexpr formula::Citation exampleRounding { .title = "Example jurisdiction",
.reference = "Example Standard 12:2020 NA",
.section = "NA.4" };
inline constexpr auto exampleJurisdiction = formula::overlay(formula::with_rounding<wholeMpa>(exampleRounding));
inline constexpr auto inExampleJurisdiction = formula::apply(exampleJurisdiction, compressiveStrength);
formula::with_rounding<rounding>(citation) replaces the method's rounding
rule. Every overlay operation needs a citation; an operation without one does
not compile. The overlaid method is run exactly like the base method:
if (!show<Cube>("cube, Example jurisdiction", inExampleJurisdiction, cube))
return 1;
std::println("");
if (!show<Cylinder>("cylinder, Example jurisdiction", inExampleJurisdiction, cylinder))
return 1;
The cube's strength is a whole number of megapascals, so it is 30 MPa under
either rule. The cylinder's is not: about 30.56 MPa rounds to 31 MPa under
the jurisdiction's rule, where the base method gives 30.6 MPa. The rounding
step of each trace taken under the jurisdiction's rule reads
rounded to 0 dp, and names the jurisdiction's overlay and its citation in
place of (method default), so a reader of the trace can see which rule
applied and where it comes from.
Output
cube, base method:
1. F = 675 kN
2. a = 150 mm
3. b = 150 mm
4. #2 * #3 = 9/400 m^2
5. #1 / #4 = 30000000 kg/(m s^2)
6. round(#5, in MPa) = 30 MPa [rounded to 1 dp (method default); nearest, ties away from zero]
7. #6 = 30 MPa [variant Cube (1st of 2), selected by tag]
cylinder, base method:
1. F = 540 kN
2. pi = 245850922/78256779
3. d = 150 mm
4. #3^2 = 9/400 m^2
5. #2 * #4 = 368776383/5217118600 m^2
6. 4
7. #5 / #6 = 368776383/20868474400 m^2
8. #1 / #7 = 3756325392000000/122925461 kg/(m s^2)
9. round(#8, in MPa) = 153/5 MPa [rounded to 1 dp (method default); nearest, ties away from zero]
10. #9 = 153/5 MPa [variant Cylinder (2nd of 2), selected by tag]
cube, Example jurisdiction:
1. F = 675 kN
2. a = 150 mm
3. b = 150 mm
4. #2 * #3 = 9/400 m^2
5. #1 / #4 = 30000000 kg/(m s^2)
6. round(#5, in MPa) = 30 MPa [rounded to 0 dp (jurisdiction overlay: Example jurisdiction, Example Standard 12:2020 NA, NA.4); nearest, ties away from zero]
7. #6 = 30 MPa [variant Cube (1st of 2), selected by tag]
cylinder, Example jurisdiction:
1. F = 540 kN
2. pi = 245850922/78256779
3. d = 150 mm
4. #3^2 = 9/400 m^2
5. #2 * #4 = 368776383/5217118600 m^2
6. 4
7. #5 / #6 = 368776383/20868474400 m^2
8. #1 / #7 = 3756325392000000/122925461 kg/(m s^2)
9. round(#8, in MPa) = 31 MPa [rounded to 0 dp (jurisdiction overlay: Example jurisdiction, Example Standard 12:2020 NA, NA.4); nearest, ties away from zero]
10. #9 = 31 MPa [variant Cylinder (2nd of 2), selected by tag]
Summary
formula::method(variants, rounding_rule, constraints)-- one method, its variants, the rule that rounds whichever ran, and its checks.formula::variant<Tag>(expression)-- the formula for the specimensTagnames.formula::explain_method<Tag>(method, environment)-- runs the variant forTag, rounds it, and records the steps; the value is in the coherent SI unit.formula::overlay(...)-- a jurisdiction's changes to a method, each with its citation.formula::with_rounding<rounding>(citation)-- replaces the method's rounding rule.formula::apply(overlay, method)-- the method with the overlay's changes made; the base method is unchanged.
Further reading
- A method: variants, tags, and one rounding rule
- An overlay yields a method
- Methods and jurisdiction overlays, which also covers constants, replaced and pruned variants, and constraints
- API reference