1. First formula
This chapter calculates one specimen's compressive strength from the maximum load it carried and the area that carried it. Along the way it introduces the four ideas everything else in the library builds on: a quantity, a formula, an environment of measurements, and a checked evaluation.
Add the library to a project
formula-cpp is header-only. With CPM.cmake
in cmake/CPM.cmake, these lines fetch it and link a program against it:
include(cmake/CPM.cmake)
CPMAddPackage("gh:LASTRADA-Software/formula-cpp@0.4.0")
add_executable(strength main.cpp)
target_compile_features(strength PRIVATE cxx_std_23)
target_link_libraries(strength PRIVATE formula-cpp::formula-cpp)
The program starts with these includes:
#include <formula-cpp/format.hpp>
#include <formula-cpp/formula.hpp>
#include <print>
formula.hpp is the library. format.hpp lets std::format and
std::print write its values. The headers for rendering, documentation and
tracing are separate, and chapters 7 and 8 introduce them.
Declare the quantities
using Load = formula::Quantity<struct LoadTag, "F", "maximum load", formula::unit::Kilonewton>;
using Area = formula::Quantity<struct AreaTag, "A_c", "loaded area", formula::unit::SquareMillimetre>;
using Strength = formula::Quantity<struct StrengthTag, "f_c", "compressive strength", formula::unit::Megapascal>;
A quantity is a type. The four template arguments used here are a tag that
makes it unique, its symbol, its description, and the unit its values are
stated in. Load is stated in kilonewtons, Area in square millimetres
and Strength in megapascals. Because the tag makes each quantity its own
type, two quantities with the same unit are still different types: a load
can never be passed where another kilonewton quantity is expected.
Write the formula
constexpr auto strength = formula::yields<Strength>(formula::var<Load> / formula::var<Area>);
formula::var<Q> stands for the value of Q, and ordinary operators combine
such values into a formula. formula::yields<Strength> names the quantity
the formula calculates. The formula is a compile-time object: declaring it
computes nothing, and no value exists until it is evaluated.
Provide the measurements
auto const specimen = formula::environment(formula::Measured<Load> { 675 }, formula::Measured<Area> { 22500 });
formula::Measured<Q> holds one value of Q, stated in Q's declared unit:
675 is a load in kilonewtons and 22500 an area in square millimetres.
formula::environment() collects the measurements a formula is evaluated
against.
Evaluate, and check the result
auto const result = formula::checked_evaluate(strength, specimen);
if (!result)
{
std::println("cannot calculate the strength: {}", result.error());
return 1;
}
std::println("{} = {} ({})", formula::symbol_of<Strength>(), *result, result->source());
formula::checked_evaluate returns a std::expected. An arithmetic failure,
such as a division by zero, is an error value: never an exception, and never
a silent zero. The program checks the result before reading it, and prints
the error and returns 1 if there is one.
The result prints as a number and its unit. formula::symbol_of<Strength>()
gives the quantity's symbol, and source() says where the value came from:
the library derived it from the formula.
Output
f_c = 30 MPa (derived)
The formula divides kilonewtons by square millimetres, and the result came out in megapascals with no conversion written; chapter 2 explains why.
Summary
formula::Quantity-- declares a quantity as a type; the four template arguments used here are tag, symbol, description and unit.formula::var<Q>-- stands for the value ofQin a formula.formula::yields<Q>-- names the quantity a formula calculates.formula::Measured<Q>-- one measured value ofQ, inQ's declared unit.formula::environment()-- collects the measurements a formula is evaluated against.formula::checked_evaluate-- evaluates a formula, returning astd::expectedthat holds the result or the arithmetic error.formula::symbol_of<Q>()-- the symbol ofQ.source()-- says where a result's value came from.