5 Quantities and units library [quantities]

5.3 Utilities [qty.utils]

5.3.4 Symbolic expressions [qty.sym.expr]

5.3.4.1 General [qty.sym.expr.general]

Subclause [qty.sym.expr] specifies the components used to maintain ordered, simplified, and readable argument lists in the names of specializations.
[Example 1: using namespace si::unit_symbols; int x = kg * km / square(h); // error: cannot construct from // derived_unit<si​::​kilo_<si​::​gram>, si​::​kilo_<si​::​metre>, per<power<non_si​::​hour, 2>>>
The library ensures decltype(kg * km / square(h)) is styled-like as commented in diagnostics, provided that, in the implementation-defined total order of types, decltype(kg) is less than decltype(km).
— end example]

5.3.4.2 Concept SymbolicConstant [qty.sym.expr.concepts]

template<typename T> concept SymbolicConstant = // exposition only std::is_empty_v<T> && std::is_final_v<T> && std::is_trivially_default_constructible_v<T> && std::is_trivially_copy_constructible_v<T> && std::is_trivially_move_constructible_v<T> && std::is_trivially_destructible_v<T>;
The concept SymbolicConstant is used to constrain the types that are used in symbolic expressions.

5.3.4.3 Types [qty.sym.expr.types]

namespace mp_units { template<typename T, typename... Ts> struct per final {}; }
per is used to store arguments with negative exponents.
A specialization of per represents the product of the inverse of its template arguments.
A program that instantiates a specialization of per that is not a possible result of the library specifications is ill-formed, no diagnostic required.
namespace mp_units { template<typename F, int Num, int... Den> requires see below struct power final { using factor = F; // exposition only static constexpr ratio exponent{Num, Den...}; // exposition only }; }
power represents a power (IEC 60050, 102-02-08) of the form .
[Note 1: 
Den is optional to shorten the type name when Den is 1.
— end note]
A program that instantiates a specialization of power that is not a possible result of the library specifications is ill-formed, no diagnostic required.
Let r be ratio{Num, Den...}.
Let is-valid-ratio be true if r is a valid constant expression, and false otherwise.
The expression in the requires-clause is equivalent to: is-valid-ratio && (r > ratio{0}) && (r != ratio{1})

5.3.4.4 Algorithms [qty.sym.expr.algos]

template<typename T> using expr-type = see below; // exposition only
expr-type<T> denotes U if T is of the form power<U, Ints...>, and T otherwise.
template<typename T, typename U> consteval bool type-less-impl(); // exposition only
Returns: true if T is less than U in an implementation-defined total order for types, and false otherwise.
template<typename Lhs, typename Rhs> struct type-less : // exposition only std::bool_constant<is-specialization-of<Rhs, power>() || type-less-impl<expr-type<Lhs>, expr-type<Rhs>>()> {};
type-less meets the requirements of the Pred parameter of the symbolic expression algorithms below.
template<typename... Ts> struct type-list {}; // exposition only template<typename OneType, typename... Ts> struct expr-fractions { // exposition only using num = see below; // exposition only using den = see below; // exposition only }
expr-fractions divides a symbolic expression to numerator and denominator parts.
Let EF be a specialization of expr-fractions.
  • If EF is of the form expr-fractions<OneType, Ts..., per<Us...>>, then
    • EF​::​num denotes type-list<Ts...>, and
    • EF​::​den denotes type-list<Us...>.
  • Otherwise, EF is of the form expr-fractions<OneType, Ts...>, and
    • EF​::​num denotes type-list<Ts...>, and
    • EF​::​den denotes type-list<>.
The symbolic expression algorithms perform operations on symbolic constants.
A symbolic constant is a type that is a model of SymbolicConstant.
[Example 1: 
The dimension dim_length, the quantity time, and the unit one are symbolic constants.
— end example]
The algorithms also support powers with a symbolic constant base and a rational exponent, products thereof, and fractions thereof.
template<template<typename...> typename To, typename OneType, template<typename, typename> typename Pred = type-less, typename Lhs, typename Rhs> consteval auto expr-multiply(Lhs, Rhs); // exposition only template<template<typename...> typename To, typename OneType, template<typename, typename> typename Pred = type-less, typename Lhs, typename Rhs> consteval auto expr-divide(Lhs lhs, Rhs rhs); // exposition only template<template<typename...> typename To, typename OneType, typename T> consteval auto expr-invert(T); // exposition only template<std::intmax_t Num, std::intmax_t Den, template<typename...> typename To, typename OneType, template<typename, typename> typename Pred = type-less, typename T> requires(Den != 0) consteval auto expr-pow(T); // exposition only
Mandates:
  • OneType is the neutral element (IEC 60050, 102-01-19) of the operation, and
  • Pred is a Cpp17BinaryTypeTrait (N4971, [meta.rqmts]) with a base characteristic of std​::​bool_constant<B>.
    Pred<T, U> implements a total order for types; B is true if T is ordered before U, and false otherwise.
Effects:
First, inputs to the operations are obtained from the types of the function parameters.
If the type of a function parameter is:
  • A specialization of To, then its input is the product of its template arguments, and the following also apply.
  • A specialization of per, then its input is the product of the inverse of its template arguments, and the following also apply.
  • A specialization of the form power<F, Num>, then its input is , or a specialization of the form power<F, Num, Den>, then its input is , and the following also applies.
  • Otherwise, the input is the symbolic constant itself.
[Example 2: 
Item by item, this algorithm step goes from the C++ parameter type decltype(km / square(h)), styled in diagnostics like derived_unit<si​::​kilo_<si​::​metre>, per<power<non_si​::​hour, 2>>,
  • to decltype(km) ×per<power<decltype(h), 2> (product of To's arguments),
  • to (product of inverse of per's arguments),
  • to (powers as powers),
  • to where and (symbolic substitution) in the mathematical domain.
— end example]
Then, the operation takes place:
  • expr-multiply multiplies its inputs,
  • expr-divide divides the input of its first parameter by the input of its second parameter,
  • expr-invert divides 1 by its input, and
  • expr-pow raises its input to the .
Finally, let r be the result of the operation simplified as follows:
  • All terms are part of the same fraction (if any).
  • There is at most a single term with a given symbolic constant.
  • There are no negative exponents.
  • 1 is only present as r and as a numerator with a denominator not equal to 1.
[Example 3: 
Item by item:

(single fraction)
(unique symbolic constants)
(positive exponents)
(non-redundant 1s)
— end example]
Returns: r is mapped to the return type:
  • If , returns OneType{}.
  • Otherwise, if r is a symbolic constant, returns r.
  • Otherwise, first applies the following mappings to the terms of r:
    • is mapped to power<x, n, d>, and is mapped to power<x, n>, and
    • 1 is mapped to OneType{}.
  • Then, a denominator x of r (if any) is mapped to per<x>.
  • Then, sorts r without per (if any) and the template arguments of per (if any) according to Pred.
  • Finally, returns To<r>{}, where per (if any) is the last argument.
Remarks: A valid template argument list for To and per is formed by interspersing commas between each mapped term.
If a mapping to std​::​intmax_t is not representable, the program is ill-formed.
expr-map maps the contents of one symbolic expression to another resulting in a different type list.
template<template<typename> typename Proj, template<typename...> typename To, typename OneType, template<typename, typename> typename Pred = type-less, typename T> consteval auto expr-map(T); // exposition only
Let
  • expr-type-map<U> be power<Proj<F>, Ints...> if U is of the form power<F, Ints...>, and Proj<U> otherwise,
  • map-power(u) be pow<Ints...>(F{}) if decltype(u) is of the form power<F, Ints...>, and u otherwise, and
  • Nums and Dens be packs denoting the template arguments of T​::​nums and T​::​dens, respectively.
Returns: (OneType{} * ... * map-power(expr-type-map<Nums>{})) / (OneType{} * ... * map-power(expr-type-map<Dens>{}))