|
Unit Conversion and Dimensional Analysis Library 3.6.1
A compile-time, header-only C++23 dimensional-analysis library
|
A closer look at the four ways to make a quantity, when to name the type versus let it be deduced, and how to read a value — and a unit — back out.
This tutorial expands on the quick tour in getting started. It assumes you have included the headers and brought in the namespaces:
There are four ways to construct a quantity, and they produce the same value. Choose whichever reads best where you are writing it.
Printing all four gives the same quantity:
meters on its own is a complete type: the compiler deduces meters<double> from the argument. This is class template argument deduction. You can always spell the representation explicitly — meters<double>, meters<float>, meters<int> — when you want a specific underlying type.
Caveat — copy-initialization from a bare double is ill-formed. meters x = 5.0; does not compile: a plain number is not a length, and the conversion is not allowed to happen implicitly (see type safety). Use direct or braced initialization for a literal number — meters x(5.0); or meters x{5.0}; — or copy-initialize from something that is a quantity:
meters x = 5.0_m; // fine: the right-hand side is already a lengthmeters y = 100.0_ft; // fine: feet convert to meters implicitly and exactlyThe one type that does copy-initialize from a bare number is a dimensionless quantity, because it has no unit to disagree with: dimensionless<double> r = 0.25; compiles.
Caveat — the decimal point selects int vs double. 5.0_m is meters<double>; 5_m is meters<int>. This mirrors the language's own rule for numeric literals. It matters because an integer-backed quantity does integer arithmetic:
std::cout << (1_m / 2_m) << '\n'; // 0 — integer division truncatesstd::cout << (1.0_m / 2.0_m) << '\n'; // 0.5 — floating-point divisionWrite the decimal point whenever you want fractional results.
When you name the type, the compiler checks that the initializer actually has that type (converting if the conversion is lossless). When you write auto, you accept whatever the expression yields.
For a simple literal this is a matter of taste. It becomes a real safety choice once arithmetic is involved — see arithmetic carries dimensionsarithmetic carries dimensions and dimensional analysis. Prefer a named type where a mistake in the dimensions would be costly; reach for auto when the result type is intermediate or verbose.
A quantity does not implicitly convert to a plain number — that would defeat the type safety. When you must hand a value to an API that does not speak units, extract it explicitly. There are three accessors:
Note — operator() is gone. In 2.x you extracted a value by calling the quantity like a function (d()). That syntax no longer exists in 3.x. Use .value(), .raw(), or .to<T>().
The one exception to "no implicit number" is a dimensionless quantity, which converts to and from arithmetic types freely, because there is no unit to lose:
Streaming a quantity writes its value followed by its unit abbreviation:
to_string gives you the same text as a std::string:
You can also query a unit's name and abbreviation directly:
name() returns the plural unit name ("meters", "feet"); abbreviation() returns the short form ("m", "ft"). Both are constexpr const char*.