Sphinx-based documentation added

This commit is contained in:
Mateusz Pusz
2020-03-09 18:55:41 +01:00
parent af6358fbdb
commit b65328d575
88 changed files with 6476 additions and 154 deletions

1
docs/CHANGELOG.md Symbolic link
View File

@@ -0,0 +1 @@
../CHANGELOG.md

104
docs/CMakeLists.txt Normal file
View File

@@ -0,0 +1,104 @@
# The MIT License (MIT)
#
# Copyright (c) 2018 Mateusz Pusz
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
find_package(Doxygen REQUIRED)
find_package(Sphinx REQUIRED)
set(DOXYGEN_INPUT_DIR "${PROJECT_SOURCE_DIR}/src")
set(DOXYGEN_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/doxygen")
set(DOXYGEN_INDEX_FILE "${DOXYGEN_OUTPUT_DIR}/xml/index.xml")
set(DOXYFILE_IN "${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in")
set(DOXYFILE_OUT "${CMAKE_CURRENT_BINARY_DIR}/Doxyfile")
# Find all the public headers
file(GLOB_RECURSE UNITS_PUBLIC_HEADERS ${DOXYGEN_INPUT_DIR}/*.h)
# Replace variables inside @@ with the current values
configure_file("${DOXYFILE_IN}" "${DOXYFILE_OUT}" @ONLY)
# Doxygen won't create this for us
file(MAKE_DIRECTORY "${DOXYGEN_OUTPUT_DIR}")
# Only regenerate Doxygen when the Doxyfile or public headers change
add_custom_command(OUTPUT "${DOXYGEN_INDEX_FILE}"
DEPENDS ${UNITS_PUBLIC_HEADERS}
COMMAND "${DOXYGEN_EXECUTABLE}" "${DOXYFILE_OUT}"
MAIN_DEPENDENCY "${DOXYFILE_OUT}" "${DOXYFILE_IN}"
COMMENT "Generating docs:"
VERBATIM)
# Nice named target so we can run the job easily
add_custom_target(doxygen ALL DEPENDS "${DOXYGEN_INDEX_FILE}")
set(SPHINX_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}")
set(SPHINX_BUILD "${CMAKE_CURRENT_BINARY_DIR}/sphinx")
set(SPHINX_INDEX_FILE "${SPHINX_BUILD}/index.html")
# Only regenerate Sphinx when:
# - Doxygen has rerun
# - Our doc files have been updated
# - The Sphinx config has been updated
add_custom_command(OUTPUT "${SPHINX_INDEX_FILE}"
COMMAND
"${SPHINX_EXECUTABLE}" -b html -j auto -Dbreathe_projects.mp-units="${DOXYGEN_OUTPUT_DIR}/xml" "${SPHINX_SOURCE}" "${SPHINX_BUILD}"
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
DEPENDS
"${CMAKE_CURRENT_SOURCE_DIR}/_static/css/custom.css"
"${CMAKE_CURRENT_SOURCE_DIR}/CHANGELOG.md"
"${CMAKE_CURRENT_SOURCE_DIR}/design.rst"
"${CMAKE_CURRENT_SOURCE_DIR}/design/quantity.rst"
"${CMAKE_CURRENT_SOURCE_DIR}/examples.rst"
"${CMAKE_CURRENT_SOURCE_DIR}/examples/hello_units.rst"
"${CMAKE_CURRENT_SOURCE_DIR}/examples/avg_speed.rst"
"${CMAKE_CURRENT_SOURCE_DIR}/faq.rst"
"${CMAKE_CURRENT_SOURCE_DIR}/framework.rst"
"${CMAKE_CURRENT_SOURCE_DIR}/framework/basic_concepts.rst"
"${CMAKE_CURRENT_SOURCE_DIR}/framework/conversions_and_casting.rst"
"${CMAKE_CURRENT_SOURCE_DIR}/framework/dimensions.rst"
"${CMAKE_CURRENT_SOURCE_DIR}/framework/quantities.rst"
"${CMAKE_CURRENT_SOURCE_DIR}/framework/text_output.rst"
"${CMAKE_CURRENT_SOURCE_DIR}/framework/units.rst"
"${CMAKE_CURRENT_SOURCE_DIR}/genindex.rst"
"${CMAKE_CURRENT_SOURCE_DIR}/glossary.rst"
"${CMAKE_CURRENT_SOURCE_DIR}/index.rst"
"${CMAKE_CURRENT_SOURCE_DIR}/introduction.rst"
"${CMAKE_CURRENT_SOURCE_DIR}/quick_start.rst"
"${CMAKE_CURRENT_SOURCE_DIR}/reference/concepts.rst"
"${CMAKE_CURRENT_SOURCE_DIR}/reference/functions.rst"
"${CMAKE_CURRENT_SOURCE_DIR}/reference/systems.rst"
"${CMAKE_CURRENT_SOURCE_DIR}/reference/types.rst"
"${CMAKE_CURRENT_SOURCE_DIR}/scenarios.rst"
"${CMAKE_CURRENT_SOURCE_DIR}/scenarios/extensions.rst"
"${CMAKE_CURRENT_SOURCE_DIR}/scenarios/legacy_interfaces.rst"
"${CMAKE_CURRENT_SOURCE_DIR}/scenarios/unknown_units_and_dimensions.rst"
"${CMAKE_CURRENT_SOURCE_DIR}/usage.rst"
"${DOXYGEN_INDEX_FILE}"
MAIN_DEPENDENCY "${SPHINX_SOURCE}/conf.py"
COMMENT "Generating documentation with Sphinx")
# Nice named target so we can run the job easily
add_custom_target(sphinx ALL DEPENDS ${SPHINX_INDEX_FILE})
# Add an install target to install the docs
include(GNUInstallDirs)
install(DIRECTORY ${SPHINX_BUILD}
DESTINATION ${CMAKE_INSTALL_DOCDIR})

965
docs/DESIGN.md Normal file
View File

@@ -0,0 +1,965 @@
# `mp-units` - Design Overview
## Summary
`mp-units` is a compile-time enabled Modern C++ library that provides compile-time dimensional
analysis and unit/quantity manipulation. The basic idea and design heavily bases on
`std::chrono::duration` and extends it to work properly with many dimensions.
Here is a small example of possible operations:
```cpp
// simple numeric operations
static_assert(10q_km / 2 == 5q_km);
// unit conversions
static_assert(1q_h == 3600q_s);
static_assert(1q_km + 1q_m == 1001q_m);
// dimension conversions
static_assert(1q_km / 1q_s == 1000q_mps);
static_assert(2q_kmph * 2q_h == 4q_km);
static_assert(2q_km / 2q_kmph == 1q_h);
static_assert(1000 / 1q_s == 1q_kHz);
static_assert(10q_km / 5q_km == 2);
```
## Approach
1. Safety and performance
- strong types
- compile-time safety
- `constexpr` all the things
- as fast or even faster than when working with fundamental types
2. The best possible user experience
- compiler errors
- debugging
3. No macros in the user interface
4. Easy extensibility
5. No external dependencies
6. Possibility to be standardized as a freestanding part of the C++ Standard Library
## Basic Concepts
The most important concepts in the library are `Unit`, `Dimension`, and `Quantity`:
![Design UML](design.png)
`Unit` is a basic building block of the library. Every dimension works with a concrete
hierarchy of units. Such hierarchy defines a reference unit and often a few scaled versions of
it.
`Dimension` concept matches a dimension of either a base or derived quantity. `base_dimension`
is instantiated with a unique symbol identifier and a base unit. `derived_unit` is a list of
exponents of either base or other derived dimensions.
`Quantity` is a concrete amount of a unit for a specified dimension with a specific
representation.
## `Unit`
All units are represented in the framework by a `scaled_unit` class template:
```cpp
template<UnitRatio R, typename U>
struct scaled_unit : downcast_base<scaled_unit<R, U>> {
using ratio = R;
using reference = U;
};
```
where:
```cpp
template<typename R>
concept UnitRatio = Ratio<R> && R::num > 0 && R::den > 0; // double negatives not allowed
```
and `Ratio` is satisfied by any instantiation of `units::ratio<Num, Den, Exp>`.
The `scaled_unit` type is a framework's private type and the user should never instantiate it directly.
The public user interface to create units consists of:
![Units UML](units.png)
All below class templates indirectly derive from a `scaled_unit` class template and satisfy a
`Unit` concept:
- `unit`
- Defines a new unnamed, in most cases coherent derived unit of a specific derived
dimension and it should be passed in this dimension's definition.
- `named_unit`
- Defines a named, in most cases base or coherent unit that is then passed to a dimension's
definition.
- A named unit may be used by other units defined with the prefix of the same type, unless
`no_prefix` is provided for `PrefixType` template parameter (in such a case it is impossible
to define a prefixed unit based on this one).
- `named_scaled_unit`
- Defines a new named unit that is a scaled version of another unit.
- Such unit can be used by other units defined with the prefix of the same type, unless
`no_prefix` is provided for `PrefixType` template parameter (in such a case it is impossible
to define a prefixed unit based on this one).
- `prefixed_unit`
- Defines a new unit that is a scaled version of another unit by the provided prefix.
- It is only possible to create such a unit if the given prefix type matches the one defined
in a reference unit.
- `deduced_unit`
- Defines a new unit with a deduced ratio and symbol based on the recipe from the provided
derived dimension.
- The number and order of provided units should match the recipe of the derived dimension.
- All of the units provided should also be a named ones so it is possible to create a deduced
symbol text.
Some of the above types depend on `PrefixType` and `no_prefix`. `PrefixType` is a concept that
is defined as:
```cpp
template<typename T>
concept PrefixType = std::derived_from<T, prefix_type>;
```
where `prefix_type` is just an empty tag type used to identify the beginning of prefix types
hierarchy and `no_prefix` is one of its children:
```cpp
struct prefix_type {};
struct no_prefix : prefix_type {};
```
Concrete prefix derives from a `prefix` class template:
```cpp
template<typename Child, PrefixType PT, basic_fixed_string Symbol, Ratio R>
requires (!std::same_as<PT, no_prefix>)
struct prefix;
```
You could notice that both units and above `prefix` class template take `Child` as a first
template parameter. `mp-units` library heavily relies on CRTP (Curiously Recurring Template
Parameter) idiom to provide the best user experience in terms of readability of compilation
errors and during debugging. It is possible thanks to the downcasting facility described later
in the design documentation.
Coming back to units, here are a few examples of unit definitions:
```cpp
namespace units::si {
// prefixes
struct prefix : prefix_type {};
struct centi : units::prefix<centi, prefix, "c", ratio<1, 1, -2>> {};
struct kilo : units::prefix<kilo, prefix, "k", ratio<1, 1, 3>> {};
// length
struct metre : named_unit<metre, "m", prefix> {};
struct centimetre : prefixed_unit<centimetre, centi, metre> {};
struct kilometre : prefixed_unit<kilometre, kilo, metre> {};
// time
struct second : named_unit<second, "s", prefix> {};
struct hour : named_scaled_unit<hour, "h", no_prefix, ratio<3600>, second> {};
// velocity
struct metre_per_second : unit<metre_per_second> {};
struct kilometre_per_hour : deduced_unit<kilometre_per_hour, dim_velocity, kilometre, hour> {};
}
namespace units::us {
// length
struct yard : named_scaled_unit<yard, "yd", no_prefix, ratio<9'144, 10'000>, si::metre> {};
struct mile : named_scaled_unit<mile, "mi", no_prefix, ratio<1'760>, yard> {};
// velocity
struct mile_per_hour : deduced_unit<mile_per_hour, si::dim_velocity, mile, si::hour> {};
}
```
Please note that thanks to C++20 features we are able to provide all information about the unit
(including text output) in a single line of its type definition. There is no need to specialize
additional type traits or use preprocessor macros.
## `Dimension`
`Dimension` is either a `BaseDimension` or a `DerivedDimension`:
```cpp
template<typename T>
concept Dimension = BaseDimension<T> || DerivedDimension<T>;
```
### `BaseDimension`
According to ISO 80000 a base quantity is a quantity in a conventionally chosen subset of a
given system of quantities, where no quantity in the subset can be expressed in terms of the
other quantities within that subset. They are referred to as being mutually independent since a
base quantity cannot be expressed as a product of powers of the other base quantities. Base unit
is a measurement unit that is adopted by convention for a base quantity in a specific system of
units.
`base_dimension` represents a dimension of a base quantity and is identified with a pair of
an unique compile-time text describing the dimension symbol and a base unit adopted for this
dimension:
```cpp
template<basic_fixed_string Symbol, Unit U>
requires U::is_named
struct base_dimension {
static constexpr auto symbol = Symbol;
using base_unit = U;
};
```
Pair of symbol and unit template parameters form an unique identifier of the base dimension.
These identifiers provide total ordering of exponents of base dimensions in a derived dimension.
The SI physical units system defines 7 base dimensions:
```cpp
namespace units::si {
struct dim_length : base_dimension<"L", metre> {};
struct dim_mass : base_dimension<"M", kilogram> {};
struct dim_time : base_dimension<"T", second> {};
struct dim_electric_current : base_dimension<"I", ampere> {};
struct dim_thermodynamic_temperature : base_dimension<"Θ", kelvin> {};
struct dim_substance : base_dimension<"N", mole> {};
struct dim_luminous_intensity : base_dimension<"J", candela> {};
}
```
All other derived quantities of SI are composed from those.
There are two reasons why a `base_dimension` gets a unit as its template parameter. First, the
base unit is needed for the text output of unnamed derived units. Second, there is more than
one system of physical units. For example CGS definitions look as follows:
```cpp
namespace units::cgs {
using si::centimetre;
using si::gram;
using si::second;
struct dim_length : base_dimension<"L", centimetre> {};
struct dim_mass : base_dimension<"M", gram> {};
using si::dim_time;
}
```
Equivalent base dimensions in different systems have the same symbol identifier and get units
from the same hierarchy (with the same reference in `scaled_unit`). Thanks to that we have
the ability to explicitly cast quantities of the same dimension from different systems or
even mix them in one `derived_dimension` definition.
### `DerivedDimension`
According to ISO 80000 a derived quantity is a quantity, in a system of quantities, defined in
terms of the base quantities of that system. Dimension of such quantity is an expression of the
dependence of a quantity on the base quantities of a system of quantities as a product of
powers of factors corresponding to the base quantities, omitting any numerical factors. A power
of a factor is the factor raised to an exponent. Each factor is the dimension of a base
quantity.
A derived dimension used internally in a library framework is implemented as a type-list like
type that stores an ordered list of exponents of one or more base dimensions:
```cpp
namespace detail {
template<Exponent E, Exponent... ERest>
requires (BaseDimension<typename E::dimension> && ... && BaseDimension<typename ERest::dimension>)
struct derived_dimension_base;
}
```
A derived dimension can be formed from multiple exponents (i.e. velocity is represented as
`exp<L, 1>, exp<T, -1>`). It is also possible to form a derived dimension with only one exponent
(i.e. frequency is represented as just `exp<T, -1>`).
Exponents are implemented with `exp` class template that provides an information about a single
dimension and its (possibly fractional) exponent in a derived dimension.
```cpp
template<Dimension Dim, std::intmax_t Num, std::intmax_t Den = 1>
struct exp {
using dimension = Dim;
static constexpr std::intmax_t num = Num;
static constexpr std::intmax_t den = Den;
};
```
In order to be able to perform computations on an arbitrary set of exponents,
`derived_dimension_base` class template have to obey the following rules:
- it contains only base dimensions in the list of exponents,
- base dimensions are not repeated in a list (the exponent of each base dimension is provided
at most once),
- exponents of base dimensions are consistently ordered,
- in case the numerator of the exponent equals zero such base dimension is erased from the list.
Above is needed for the framework to provide dimensional analysis. However, sometimes it is
useful to define derived units in terms of other derived units. To support this both a base
dimension and a derived dimension can be provided to `exp` class template.
As it was stated above `derived_dimension_base` is a private utility of the framework. In order
to define a new derived dimension the user has to instantiate the following class template:
```cpp
template<typename Child, Unit U, Exponent E, Exponent... ERest>
struct derived_dimension : downcast_child<Child, typename detail::make_dimension<E, ERest...>> {
using recipe = exp_list<E, ERest...>;
using coherent_unit = U;
using base_units_ratio = /* see below */;
};
```
There are a few important differences between `detail::derived_dimension_base` and
`derived_dimension`. First, the latter one gets the coherent unit of the derived dimension.
According to ISO 80000 a coherent unit is a unit that, for a given system of quantities and for
a chosen set of base units, is a product of powers of base units with no other proportionality
factor than one.
The other difference is that `derived_dimension` allows to provide other derived dimensions in
the list of its exponents. This is called a "recipe" of the dimension and among others is used
to print unnamed coherent units of this dimension.
In case a derived dimension appears on the list of exponents, such derived dimension will be
unpacked, sorted, and consolidated by a `detail::make_dimension` helper to form a valid list
of exponents of only base dimensions later provided to `detail::derived_dimension_base`.
Sometimes units of equivalent quantities in different systems of units do not share the same
reference so they cannot be easily converted to each other. An example can be a pressure for
which a coherent unit in SI is pascal and in CGS barye. Those two units are not directly
related with each other with some ratio. As they both are coherent units of their dimensions,
the ratio between them is directly determined by the ratios of base units defined in base
dimensions end their exponents in the derived dimension recipe. To provide interoperability of
such quantities of different systems `base_units_ratio` is being used. The result of the
division of two `base_units_ratio` of two quantities of equivalent dimensions in two different
systems gives a ratio between their coherent units. Alternatively, the user would always have to
directly define a barye in terms of pascal or vice versa.
Below are a few examples of derived dimension definitions:
```cpp
namespace units::si {
struct dim_velocity : derived_dimension<dim_velocity, metre_per_second,
exp<dim_length, 1>, exp<dim_time, -1>> {};
struct dim_acceleration : derived_dimension<dim_acceleration, metre_per_second_sq,
exp<dim_length, 1>, exp<dim_time, -2>> {};
struct dim_force : derived_dimension<dim_force, newton,
exp<dim_mass, 1>, exp<dim_acceleration, 1>> {};
struct dim_energy : derived_dimension<dim_energy, joule,
exp<dim_force, 1>, exp<dim_length, 1>> {};
struct dim_power : derived_dimension<dim_power, watt,
exp<dim_energy, 1>, exp<dim_time, -1>> {};
}
```
If as a result of dimensional computation the library framework will generate a derived
dimension that was not predefined by the user than the instance of
`unknown_dimension<Exponent...>`. The coherent unit of such an unknown dimension is
`scaled_unit<ratio<1>, unknown_coherent_unit>`.
## `Quantity`
`quantity` is a class template that expresses the quantity/amount of a specific dimension
expressed in a specific unit of that dimension:
```cpp
template<Dimension D, UnitOf<D> U, Scalar Rep = double>
class quantity
```
`quantity` provides a similar interface to `std::chrono::duration`. The difference is that it
uses `double` as a default representation and has a few additional member types and
functions as below:
```cpp
template<Dimension D, UnitOf<D> U, Scalar Rep = double>
class quantity {
public:
using dimension = D;
using unit = U;
using rep = Rep;
[[nodiscard]] static constexpr quantity one() noexcept;
// ...
};
template<typename D1, typename U1, typename Rep1, typename D2, typename U2, typename Rep2>
requires detail::basic_arithmetic<Rep1, Rep2> && equivalent_dim<D1, dim_invert<D2>>
[[nodiscard]] constexpr Scalar auto operator*(const quantity<D1, U1, Rep1>& lhs,
const quantity<D2, U2, Rep2>& rhs);
template<typename D1, typename U1, typename Rep1, typename D2, typename U2, typename Rep2>
requires detail::basic_arithmetic<Rep1, Rep2> && (!equivalent_dim<D1, dim_invert<D2>>)
[[nodiscard]] constexpr Quantity auto operator*(const quantity<D1, U1, Rep1>& lhs,
const quantity<D2, U2, Rep2>& rhs);
template<Scalar Value, typename D, typename U, typename Rep>
requires std::magma<std::ranges::divided_by, Value, Rep>
[[nodiscard]] constexpr Quantity auto operator/(const Value& v,
const quantity<D, U, Rep>& q);
template<typename D1, typename U1, typename Rep1, typename D2, typename U2, typename Rep2>
requires detail::basic_arithmetic<Rep1, Rep2> && equivalent_dim<D1, D2>
[[nodiscard]] constexpr Scalar auto operator/(const quantity<D1, U1, Rep1>& lhs,
const quantity<D2, U2, Rep2>& rhs);
template<typename D1, typename U1, typename Rep1, typename D2, typename U2, typename Rep2>
requires detail::basic_arithmetic<Rep1, Rep2> && (!equivalent_dim<D1, D2>)
[[nodiscard]] constexpr Quantity AUTO operator/(const quantity<D1, U1, Rep1>& lhs,
const quantity<D2, U2, Rep2>& rhs);
```
Additional functions provide the support for operations that result in a different dimension
type than those of their arguments. `equivalent_dim` constraint requires two dimensions to be
either the same or have convertible units of base dimension (with the same reference unit).
Beside adding new elements a few other changes where applied compared to the `std::chrono::duration` class:
1. The `duration` is using `std::common_type_t<Rep1, Rep2>` to find a common representation
for a calculation result. Such a design was reported as problematic by SG6 (numerics study group) members
as sometimes we want to provide a different type in case of multiplication and different in case of
division. `std::common_type` lacks that additional information. That is why `units::quantity` uses
the resulting type of a concrete operator operation.
2. `operator %` is constrained with `treat_as_floating_point` type trait to limit the types to integral
representations only. Also `operator %(Rep)` takes `Rep` as a template argument to limit implicit
conversions.
To simplify writing efficient generic code quantities of each dimension have associated:
1. Concept (i.e. `units::Length`) that matches a length dimension of any physical systems.
2. Per-system quantity alias (i.e. `units::si::length<Unit, Rep>` for
`units::quantity<units::si::dim_length, Unit, Rep>`).
Also, to help instantiate quantities with compile-time known values every unit in the library
has an associated UDL. For example:
```cpp
namespace si::inline literals {
// m
constexpr auto operator"" q_m(unsigned long long l) { return length<metre, std::int64_t>(l); }
constexpr auto operator"" q_m(long double l) { return length<metre, long double>(l); }
// km
constexpr auto operator"" q_km(unsigned long long l) { return length<kilometre, std::int64_t>(l); }
constexpr auto operator"" q_km(long double l) { return length<kilometre, long double>(l); }
}
```
### `quantity_cast`
To explicitly force truncating conversions `quantity_cast` function is provided which is a direct
counterpart of `std::chrono::duration_cast`. As a template argument user can provide here either
a `quantity` type or only its template parameters (`Dimension`, `Unit`, or `Rep`):
```cpp
template<Quantity To, typename D, typename U, typename Rep>
requires QuantityOf<To, D> &&
detail::basic_arithmetic<std::common_type_t<typename To::rep, Rep, intmax_t>>
[[nodiscard]] constexpr auto quantity_cast(const quantity<D, U, Rep>& q);
template<Dimension ToD, typename D, typename U, typename Rep>
requires equivalent_dim<ToD, D>
[[nodiscard]] constexpr auto quantity_cast(const quantity<D, U, Rep>& q);
template<Unit ToU, typename D, typename U, typename Rep>
requires UnitOf<ToU, D>
[[nodiscard]] constexpr auto quantity_cast(const quantity<D, U, Rep>& q);
template<Scalar ToRep, typename D, typename U, typename Rep>
requires detail::basic_arithmetic<std::common_type_t<ToRep, Rep, intmax_t>>
[[nodiscard]] constexpr auto quantity_cast(const quantity<D, U, Rep>& q);
```
## Text output
### Unit Symbol
The library tries its best to print a correct unit of the quantity. This is why it performs
a series of checks:
1. If the user predefined a unit with a `named_XXX_unit` class templates, the symbol provided
by the user will be used (i.e. `60 W`).
2. If a unit was created with a `deduced_unit` class template, the symbol of deduced unit is
printed (i.e. `70 km/h`).
3. Otherwise, the library tries to print a prefix and symbol of an unknown unit for this derived
dimension:
- prefix:
- if ratio of the scaled unit is `1`, than no prefix is being printed,
- otherwise, if `PrefixType` template parameter of a reference unit is different than
`no_prefix`, and if the ratio of scaled unit matches the ratio of a prefix of a specified
type, than the symbol of this prefix will be used,
- otherwise, non-standard ratio (i.e. `2 [60]Hz`) will be printed.
- symbol:
- if a reference unit has a user-predefined or deduced symbol, than this symbol it is being
printed,
- otherwise, the symbol is constructed from names and exponents of base dimensions
(i.e. `2 m/kg^2`).
### `operator<<`
`quantity::operator<<()` provides only a basic support to print a quantity. It prints its count
and a symbol separated with one space character.
### Text Formatting
`mp-units` supports new C++20 formatting facility (currently provided as a dependency on
[`fmt`](https://github.com/fmtlib/fmt) library). `parse()` member functions of
`fmt::formatter<units::quantity<Dimension, Unit, Rep>, CharT>` class template partial
specialization interprets the format specification as a `units-format-spec` according to the
following syntax:
```text
units-format-spec:
fill-and-align[opt] sign[opt] width[opt] precision[opt] units-specs[opt]
units-specs:
conversion-spec
units-specs conversion-spec
units-specs literal-char
literal-char:
any character other than { or }
conversion-spec:
% modifier[opt] type
modifier: one of
E O
type: one of
n q Q t %
```
The productions `fill-and-align`, `sign`, `width`, and `precision` are described in
[Format string](https://wg21.link/format.string.std) chapter of the C++ standard. Giving a
`precision` specification in the `units-format-spec` is valid only for `units::quantity` types
where the representation type `Rep` is a floating-point type. For all other `Rep` types, an
exception of type `format_error` is thrown if the `units-format-spec` contains a precision
specification. An `format_error` is also thrown if `sign` is provided with a `conversion-spec`
to print quantity unit but not its value.
Each conversion specifier `conversion-spec` is replaced by appropriate characters as described
in the following table:
| Specifier | Replacement |
|:---------:|---------------------------------------------------------------|
| `%n` | A new-line character |
| `%q` | The quantitys unit symbol |
| `%Q` | The quantitys numeric value (as if extracted via `.count()`) |
| `%t` | A horizontal-tab character |
| `%%` | A `%` character |
If the `units-specs` is omitted, the `quantity` object is formatted as if by streaming it to
`std::ostringstream os` and copying `os.str()` through the output iterator of the context with
additional padding and adjustments as specified by the format specifiers.
```cpp
std::string s = fmt::format("{:=>12}", 120q_kmph); // value of s is "====120 km/h"
```
## Improving user's experience
Most of the important design decisions in the library are dictated by the requirement of
providing the best user experience as possible.
Most of C++ libraries in the world use template aliases to provide a friendly name for a
developer. Unfortunately, such aliases are quickly lost in a compilation process and as a
result the potential error log contains a huge source type rather than a short alias for it.
The same can be observed during debugging of a code using template aliases.
Let's assume that we want to provide a user friendly name for a capacitance derived dimension.
Other libraries will do it in the following way:
```cpp
using dim_capacitance = detail::derived_dimension_base<exp<si::dim_electric_current, 2>,
exp<si::dim_length, -2>,
exp<si::dim_mass, -1>,
exp<si::dim_time, 4>>;
```
The above solution does provide a good developer's experience but a really poor one for the end
user. If we will get a compilation error message containing `dim_capacitance` in most cases
the compiler will print the following type instead of the alias:
```text
units::detail::derived_dimension_base<units::exp<units::si::dim_electric_current, 2, 1>,
units::exp<units::si::dim_length, -2, 1>, units::exp<units::si::dim_mass, -1, 1>,
units::exp<units::si::dim_time, 4, 1> >
```
You can notice that even this long syntax was carefully selected to provide quite good user
experience (some other units libraries produce a type that cannot easily fit on one slide)
but it is not questionable less readable than just `dim_capacitance`.
NOTE: To better understand how the framework works and not clutter the text and graphs with
long types in the following examples we will switch from `dim_capacitance` to `dim_area`.
The latter one has much shorter definition but the end result for both will be exactly the same.
User-friendly, short name printed by the compiler and the debugger.
To fix it we have to provide a strong type. As we do not have opaque/strong typedefs
in the language we have to use inheritance:
![UML](downcast_1.png)
This gives us a nice looking strong type but does not solve the problem of how to switch from
a long instantiation of a `derived_dimension_base` class template that was generated by the
framework as a result of dimensional calculation to a child class assigned by the user for this
instantiation.
### Downcasting facility
To support this `mp-units` library introduces a new downcasting facility implemented fully as
a library feature. It creates 1-to-1 link between a long class template instantiation and a
strong type provided by the user. This means that only one child class can be created for a
specific base class template instantiation.
Downcasting facility is provided by injecting two classes into our hierarchy:
![UML](downcast_2.png)
In the above example `dim_area` is a downcasting target (child class) and a specific
`detail::derived_dimension` class template instantiation is a downcasting source (base class).
```cpp
template<typename BaseType>
struct downcast_base {
using downcast_base_type = BaseType;
friend auto downcast_guide(downcast_base); // declaration only (no implementation)
};
```
`units::downcast_base` is a class that implements CRTP idiom, marks the base of downcasting
facility with a `downcast_base_type` member type, and provides a declaration of downcasting ADL
friendly (Hidden Friend) entry point member function `downcast_guide`. An important design point
is that this function does not return any specific type in its declaration. This non-member
function is going to be defined in a child class template `downcast_child` and will return a
target type of the downcasting operation there.
```cpp
template<typename T>
concept Downcastable =
requires {
typename T::downcast_base_type;
} &&
std::derived_from<T, downcast_base<typename T::downcast_base_type>>;
```
`units::Downcastable` is a concepts that verifies if a type implements and can be used in a
downcasting facility.
```cpp
template<typename Target, Downcastable T>
struct downcast_child : T {
friend auto downcast_guide(typename downcast_child::downcast_base) { return Target(); }
};
```
`units::downcast_child` is another CRTP class template that provides the implementation of a
non-member friend function of the `downcast_base` class template which defines the target
type of a downcasting operation.
With such CRTP types the only thing the user has to do to register a new type to the downcasting
facility is to publicly derive from one of those CRTP types and provide its new child type as
the first template parameter of the CRTP type.
Above types are used to define base and target of a downcasting operation. To perform the actual
downcasting operation a dedicated template alias is provided:
```cpp
template<Downcastable T>
using downcast = decltype(detail::downcast_target_impl<T>());
```
`units::downcast` is used to obtain the target type of the downcasting operation registered
for a given instantiation in a base type. `detail::downcast_target_impl` checks if a downcasting
target is registered for the specific base class. If yes, it returns the registered type,
otherwise it works like a regular identity type returning a provided base class.
```cpp
namespace detail {
template<typename T>
concept has_downcast = requires {
downcast_guide(std::declval<downcast_base<T>>());
};
template<typename T>
constexpr auto downcast_target_impl()
{
if constexpr(has_downcast<T>)
return decltype(downcast_guide(std::declval<downcast_base<T>>()))();
else
return T();
}
}
```
Additionally there is one more simple helper alias provided that is used in the internal
library implementation:
```cpp
template<Downcastable T>
using downcast_base_t = T::downcast_base_type;
```
### `unknown_dimension<Exponent...>`
Sometimes dimensional calculation results with a class template instantiation that was not
predefined by the user in the downcasting facility. A typical example of such a case are
temporary results of calculations:
```cpp
units::Length auto d1 = 123q_m;
units::Time auto t1 = 10q_s;
units::Velocity auto v1 = avg_speed(d1, t1);
auto temp1 = v1 * 50q_m; // intermediate unknown dimension
units::Velocity auto v2 = temp1 / 100q_m; // back to known dimensions again
units::Length auto d2 = v2 * 60q_s;
```
To provide support to form an unknown derived dimension that could be than be converted to a
known one with a correct unit, and also to improve the user experience and clearly state that
it is an unknown dimension the library framework will provide an instance of:
```cpp
struct unknown_coherent_unit : unit<unknown_coherent_unit> {};
template<Exponent E, Exponent... ERest>
struct unknown_dimension : derived_dimension<unknown_dimension<E, ERest...>,
scaled_unit<ratio<1>, unknown_coherent_unit>,
E, ERest...> {
using coherent_unit = scaled_unit<ratio<1>, unknown_coherent_unit>;
};
```
with this the error log or a debugger breakpoint involving a `temp1` type will include:
```text
units::quantity<units::unknown_dimension<units::exp<units::si::dim_length, 2, 1>,
units::exp<units::si::dim_time, -1, 1> >, units::unknown_coherent_unit, long int>
```
## Extensibility
The library was designed with a simple extensibility in mind. It is easy to add new units,
dimensions, and prefixes. The systems of units are not closed (classes) but open (namespaces)
and can be easily extended, or its content can be partially/fully imported to other systems.
### Adding a new system with custom dimensions and units
A great example of a adding a whole new system can be a `data` system in the library which
adds support for digital information quantities. In summary it adds:
1. New prefix type and its prefixes:
```cpp
namespace units::data {
struct prefix : prefix_type {};
struct kibi : units::prefix<kibi, prefix, "Ki", ratio< 1'024>> {};
struct mebi : units::prefix<mebi, prefix, "Mi", ratio<1'048'576>> {};
}
```
2. New units for `information`:
```cpp
namespace units::data {
struct bit : named_unit<bit, "b", prefix> {};
struct kibibit : prefixed_unit<kibibit, kibi, bit> {};
struct byte : named_scaled_unit<byte, "B", prefix, ratio<8>, bit> {};
struct kibibyte : prefixed_unit<kibibyte, kibi, byte> {};
}
```
3. New base dimension, its concept, and quantity alias:
```cpp
namespace units::data {
struct dim_information : base_dimension<"information", bit> {};
template<typename T>
concept Information = QuantityOf<T, dim_information>;
template<Unit U, Scalar Rep = double>
using information = quantity<dim_information, U, Rep>;
}
```
4. UDLs for new units
```cpp
namespace units::data::inline literals {
// bits
constexpr auto operator"" q_b(unsigned long long l) { return information<bit, std::int64_t>(l); }
constexpr auto operator"" q_Kib(unsigned long long l) { return information<kibibit, std::int64_t>(l); }
// bytes
constexpr auto operator"" q_B(unsigned long long l) { return information<byte, std::int64_t>(l); }
constexpr auto operator"" q_KiB(unsigned long long l) { return information<kibibyte, std::int64_t>(l); }
}
```
5. A new `bitrate` derived dimension, its units, concept, quantity helper, and UDLs
```cpp
namespace units::data {
struct bit_per_second : unit<bit_per_second> {};
struct dim_bitrate : derived_dimension<dim_bitrate, bit_per_second, exp<dim_information, 1>, exp<si::dim_time, -1>> {};
struct kibibit_per_second : deduced_unit<kibibit_per_second, dim_bitrate, kibibit, si::second> {};
template<typename T>
concept Bitrate = QuantityOf<T, dim_bitrate>;
template<Unit U, Scalar Rep = double>
using bitrate = quantity<dim_bitrate, U, Rep>;
inline namespace literals {
// bits
constexpr auto operator"" q_bps(unsigned long long l) { return bitrate<bit_per_second, std::int64_t>(l); }
constexpr auto operator"" q_Kibps(unsigned long long l) { return bitrate<kibibit_per_second, std::int64_t>(l); }
}
}
```
### Using custom representations
In theory `quantity` can take any arithmetic-like type as a `Rep` template parameter. In
practice some interface is forced by numeric concepts.
To provide basic library functionality the type should satisfy the `Scalar` concept:
```cpp
template<typename T, typename U = T>
concept basic-arithmetic = // exposition only
std::magma<std::ranges::plus, T, U> &&
std::magma<std::ranges::minus, T, U> &&
std::magma<std::ranges::times, T, U> &&
std::magma<std::ranges::divided_by, T, U>;
template<typename T>
concept Scalar =
(!Quantity<T>) &&
(!WrappedQuantity<T>) &&
std::regular<T> &&
std::totally_ordered<T> &&
basic-arithmetic<T>;
```
Where `WrappedQuantity` is a concept that applies `Quantity<typename T::value_type>` recursively
on all nested types to check if `T` is not actually a wrapped quantity type (i.e. a vector or
matrix of quantities).
The above implies that the `Rep` type should provide at least:
- default constructor, destructor, copy-constructor, and copy-assignment operator
- `operator==(Rep, Rep)`, `operator!=(Rep, Rep)`
- `operator<(Rep, Rep)`, `operator>(Rep, Rep)`, `operator<=(Rep, Rep)`, `operator>=(Rep, Rep)`
- `operator-(Rep)`
- `operator+(Rep, Rep)`, `operator-(Rep, Rep)`, `operator*(Rep, Rep)`, `operator*(Rep, Rep)`
Above also requires that the `Rep` should be implicitly convertible from integral types
(i.e. `int`) so a proper implicit converting constructor should be provided.
Moreover, in most cases to observe expected behavior `Rep` will have to be registered as a
floating-point representation type by specializing `units::treat_as_floating_point` type
trait:
```cpp
template<typename Rep>
inline constexpr bool treat_as_floating_point;
```
An example of such a type can be found in [measurement example](../example/measurement.cpp).
However, as written above this will enable only a basic functionality of the library. In case
additional `quantity` operations are needed the user may opt-in to any of them by providing
the equivalent operation for `Rep` type. Here is an additional list of opt-in operations:
- `operator++()`
- `operator++(int)`
- `operator--()`
- `operator--(int)`
- `operator+=(Rep)`
- `operator-=(Rep)`
- `operator*=(Rep)`
- `operator/=(Rep)`
- `operator%=(Rep)`
- `operator%(Rep, Rep)`
`quantity` also has 4 static functions `zero()`, `one()`, `min()`, and `max()` which can
be enabled by providing a specialization of `quantity_values` type trait for `Rep` type:
```cpp
template<Scalar Rep>
struct quantity_values;
```
## FAQ
1. Why all UDLs are prefixed with `q_` instead of just using unit symbol?
Usage of only unit symbols in UDLs would be a preferred approach (less to type, easier to
understand and maintain). However, while increasing the coverage for the library we learned
that there are a lot unit symbols that conflict with built-in types or numeric extensions.
A few of those are: `F` (farad), `J` (joule), `W` (watt), `K` (kelvin), `d` (day), `l` or
`L` (litre), `erg`, `ergps`. For a while we had to used `_` prefix to make the library work
at all but at some point we had to unify the naming and we came up with `q_` prefix which
results in a creation of quantity of a provided unit.
2. Why dimensions depend on units and not vice versa?
Most of the libraries define units in terms of dimensions and this was also an initial
approach for this library. However it turns out that for such a design it is hard to provide
support for all the required scenarios.
The first of them is to support multiple unit systems (like SI, CGS, ...) where each of
can have a different base unit for the same dimension. Base quantity of dimension length in
SI has to know that it should use `m` to print the unit symbol to the text output, while
the same dimension for CGS should use `cm`. Also it helps in conversions among those systems.
The second one is to support natural units where more than one dimension can be measured
with the same unit (i.e. `GeV`). Also if someone will decide to implement a systems where
SI quantities of the same kind are expressed as different dimensions (i.e. height, width,
and depth) all of them will just be measured in meters.
3. Why do we spell `metre` instead of `meter`?

86
docs/Doxyfile.in Normal file
View File

@@ -0,0 +1,86 @@
# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
# into which the generated documentation will be written. If a relative path is
# entered, it will be relative to the location where doxygen was started. If
# left blank the current directory will be used.
OUTPUT_DIRECTORY = "@DOXYGEN_OUTPUT_DIR@"
# The INPUT tag is used to specify the files and/or directories that contain
# documented source files. You may enter file names like myfile.cpp or
# directories like /usr/src/myproject. Separate the files or directories with
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched.
INPUT = "@DOXYGEN_INPUT_DIR@"
# The RECURSIVE tag can be used to specify whether or not subdirectories should
# be searched for input files as well.
# The default value is: NO.
RECURSIVE = YES
# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
# to include (a tag file for) the STL sources as input, then you should set this
# tag to YES in order to let doxygen match functions declarations and
# definitions whose arguments contain STL classes (e.g. func(std::string);
# versus func(std::string) {}). This also make the inheritance and collaboration
# diagrams that involve STL classes more complete and accurate.
# The default value is: NO.
BUILTIN_STL_SUPPORT = YES
# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
# The default value is: YES.
GENERATE_HTML = NO
# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
# The default value is: YES.
GENERATE_LATEX = NO
# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that
# captures the structure of the code including all documentation.
# The default value is: NO.
GENERATE_XML = YES
# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
# documentation are documented, even if no documentation was available. Private
# class members and static file members will be hidden unless the
# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
# Note: This will also disable the warnings about undocumented members that are
# normally produced when WARNINGS is set to YES.
# The default value is: NO.
EXTRACT_ALL = YES
# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
# included in the documentation.
# The default value is: NO.
EXTRACT_STATIC = YES
# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
# (namespaces, classes, functions, etc.) that should be excluded from the
# output. The symbol name can be a fully qualified name, a word, or if the
# wildcard * is used, a substring. Examples: ANamespace, AClass,
# AClass::ANamespace, ANamespace::*Test
#
# Note that the wildcards are matched against the file with absolute path, so to
# exclude all test directories use the pattern */test/*
EXCLUDE_SYMBOLS = units::detail
# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
# will automatically be disabled.
# The default value is: YES.
WARN_IF_UNDOCUMENTED = YES
# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
# a warning is encountered.
# The default value is: NO.
WARN_AS_ERROR = NO

157
docs/INSTALL.md Normal file
View File

@@ -0,0 +1,157 @@
# Installation Guide
## Repository structure
This repository contains three independent `cmake`-based projects:
1. `./src`
- header-only project containing whole `mp-units` library
2. `.`
- project used as an entry point for library development (it wraps `./src` project
together with usage examples and tests)
3. `./test_package` - library installation and Conan package verification
NOTE: Please note that this repository depends on a git submodule in the `./cmake/common`
subdirectory.
## Installation and Reuse
There are a few different ways of installing/reusing `units` in your project.
### Conan quick intro
In case you are not familiar with `conan`, to install it just do:
```shell
pip3 install -U conan
```
After that you might need to add a custom profile in `~/.conan/profile` for your
developnment environment. An example profile can look as follows:
```text
[settings]
os=Linux
os_build=Linux
arch=x86_64
arch_build=x86_64
compiler=gcc
compiler.version=9
compiler.cppstd=20
compiler.libcxx=libstdc++11
build_type=Release
[options]
[build_requires]
[env]
CC=/usr/bin/gcc-9
CXX=/usr/bin/g++-9
```
### Copy
As `units` is a header-only library you can simply copy `src/include` directory to
your source tree and use it as regular header files.
NOTE: Until C++20 arrives the library has some 3rd party dependencies that provide
experimental C++20 features. The list of dependencies include:
- `range-v3@ericniebler` (only for gcc-9, gcc-10 uses gcc's concepts implementation)
- `fmt@_`
All of them are easily to obtain with `conan`.
NOTE: In case a full library's repository is to be compiled (instead of just copying
`src/include` headers), additionally, the library's unit tests depend on
`Catch2@catchorg` and `linear_algebra@public-conan` conan packages.
### cmake + conan
To use `units` as a `cmake` imported library via `cmake` configuration files the following
steps may be done:
- clone the repository with its submodules:
```shell
git clone --recurse-submodules https://github.com/mpusz/units.git
```
or in case it is already cloned without submodules initialize, fetch, and checkout them with:
```shell
git submodule update --init
```
- add the following remotes to your local `conan` instance
```shell
conan remote add conan-mpusz https://api.bintray.com/conan/mpusz/conan-mpusz
```
- add `units` as a dependency to your `conan` file. For example to use testing version of
`0.5.0` of `mp-units` add:
- `conanfile.txt`
```ini
[requires]
mp-units/0.5.0@mpusz/testing
```
- `conanfile.py`
```python
requires = "mp-units/0.5.0@mpusz/testing"
```
- import `conan` dependencies to top level `CMakeLists.txt` file
```cmake
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup(TARGETS)
```
- link your `cmake` target with units
```cmake
target_link_libraries(<your_target> PUBLIC|PRIVATE|INTERFACE CONAN_PKG::mp-units)
```
- install `conan` dependencies before configuring cmake
```shell
cd build
conan install .. -pr <your_conan_profile> -s compiler.cppstd=20 -b=outdated -u
```
## Full build and unit testing
In case you would like to build all the code in this repository (with unit tests and examples)
you should use the `CMakeLists.txt` from the parent directory and run Conan with
`CONAN_RUN_TESTS=True`.
```shell
git clone --recurse-submodules https://github.com/mpusz/units.git
mkdir units/build && cd units/build
conan remote add linear_algebra https://api.bintray.com/conan/twonington/public-conan
conan install .. -pr <your_conan_profile> -s compiler.cppstd=20 -e CONAN_RUN_TESTS=True -b outdated
cmake .. -DCMAKE_BUILD_TYPE=Release
cmake --build .
ctest -VV
```
## Packaging
To create a `conan` package and test `cmake` installation and `conan` packaging run:
```shell
conan create . <username>/<channel> -pr <your_conan_profile> -s compiler.cppstd=20 -e CONAN_RUN_TESTS=True -b outdated
```
## Upload package to conan server
```shell
conan upload -r <remote-name> --all mp-units/0.5.0@<user>/<channel>
```

115
docs/README.md Normal file
View File

@@ -0,0 +1,115 @@
# `mp-units` - Documentation
## How to build?
1. Install the requirements (Sphinx) with:
```shell
pip3 install -r docs/requirements.txt
```
2. Install all dependencies with Conan for a developer's build:
```shell
conan install .. -pr <your_conan_profile> -s compiler.cppstd=20 -e CONAN_RUN_TESTS=True -b outdated
```
3. Install Python 3
4. Build the documentation with a regular CMake build
## How to contribute?
To make any contribution to **mp-units** documentation please fork this repository and open
a Pull Request.
### Style Guidelines
This guidelines are just general good practices for the formatting and structure of the whole
documentation and do not pretend to be a stopper for any helpful contribution. Any contribution
that may include relevant information for **mp-units** users will always be welcomed.
**mp-units** documentation is written in [reStructuredText](http://docutils.sourceforge.net/rst.html)
and follows [reStructuredText Markup Specification](http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html).
[Quick reStructuredText](http://docutils.sourceforge.net/docs/user/rst/quickref.html) is also
used for reference.
Any detail not covered by this guidelines will follow the aforementioned rules.
#### Section titles
Use section titles in this level of importance:
```rst
Section Title
=============
Subsection Title
----------------
Subsubsection Title
^^^^^^^^^^^^^^^^^^^
```
#### Text emphasis/highlighting
- **Bold text** to highlight important text:
```rst
**mp-units** is a compile-time enabled Modern C++ library that provides compile-time dimensional
analysis and unit/quantity manipulation.
```
- *Italics* to refer to file names, directory names, and paths.
```rst
Create Conan configuration file (either *conanfile.txt* or *conanfile.py*) in your project's
top-level directory...
```
- ``Inline literals`` to refer to the in examples that is not a part of the **mp-units** library:
```rst
Let's assume that the user wants to implement an ``avg_speed`` function that will
be calculating the average speed based on provided distance and duration quantities.
```
#### Literal blocks
Most of the C++ code examples should be provided as literal blocks after double `::` symbol:
```rst
For this dimension-specific concepts come handy again and with usage of C++20 generic
functions our function can look as simple as::
constexpr Velocity auto avg_speed(Length auto d, Time auto t)
{
return d / t;
}
```
#### code-blocks
Use code-blocks for exceptional cases like code samples in other languages or a need
to emphasize specific lines of code:
```rst
Quantities of the same dimension can be easily added or subtracted with
each other and the result will always be a quantity of the same dimension:
.. code-block::
:emphasize-lines: 3-4
Length auto dist1 = 2q_m;
Length auto dist2 = 1q_m;
Length auto res1 = dist1 + dist2;
Length auto res2 = dist1 - dist2;
```
#### Indentation and line length
Make sure all indentation is done with spaces. Normally 2 space indentation for bulleted lists
and 4 space indentation for code blocks and RST directives contents:
Do not leave any unnecessary or trailing spaces.

20
docs/_static/css/custom.css vendored Normal file
View File

@@ -0,0 +1,20 @@
@import 'theme.css';
a.reference.internal + code.sig-name.descname {
padding-left: 4px;
}
.breatheparameterlist li tt + p {
display: inline;
}
.breatheenumvalues li tt + p {
display: inline;
}
.rst-content .admonition-try-it-on-compiler-explorer {
background-color: #e2f6d5;
}
.rst-content .admonition-try-it-on-compiler-explorer > .admonition-title {
background-color: #66c52a;
}

BIN
docs/_static/img/design.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -0,0 +1,32 @@
"""
Sphinx Read the Docs theme.
From https://github.com/ryan-roemer/sphinx-bootstrap-theme.
"""
from os import path
import sphinx
__version__ = '0.4.3.dev0'
__version_full__ = __version__
def get_html_theme_path():
"""Return list of HTML theme paths."""
cur_dir = path.abspath(path.dirname(path.dirname(__file__)))
return cur_dir
# See http://www.sphinx-doc.org/en/stable/theming.html#distribute-your-theme-as-a-python-package
def setup(app):
app.add_html_theme('sphinx_rtd_theme', path.abspath(path.dirname(__file__)))
if sphinx.version_info >= (1, 8, 0):
# Add Sphinx message catalog for newer versions of Sphinx
# See http://www.sphinx-doc.org/en/master/extdev/appapi.html#sphinx.application.Sphinx.add_message_catalog
rtd_locale_path = path.join(path.abspath(path.dirname(__file__)), 'locale')
app.add_message_catalog('sphinx', rtd_locale_path)
return {'parallel_read_safe': True, 'parallel_write_safe': True}

View File

@@ -0,0 +1,82 @@
{# Support for Sphinx 1.3+ page_source_suffix, but don't break old builds. #}
{% if page_source_suffix %}
{% set suffix = page_source_suffix %}
{% else %}
{% set suffix = source_suffix %}
{% endif %}
{% if meta is defined and meta is not none %}
{% set check_meta = True %}
{% else %}
{% set check_meta = False %}
{% endif %}
{% if check_meta and 'github_url' in meta %}
{% set display_github = True %}
{% endif %}
{% if check_meta and 'bitbucket_url' in meta %}
{% set display_bitbucket = True %}
{% endif %}
{% if check_meta and 'gitlab_url' in meta %}
{% set display_gitlab = True %}
{% endif %}
<div role="navigation" aria-label="breadcrumbs navigation">
<ul class="wy-breadcrumbs">
{% block breadcrumbs %}
<li><a href="{{ pathto(master_doc) }}" class="icon icon-home"></a> &raquo;</li>
{% for doc in parents %}
<li><a href="{{ doc.link|e }}">{{ doc.title }}</a> &raquo;</li>
{% endfor %}
<li>{{ title }}</li>
{% endblock %}
{% block breadcrumbs_aside %}
<li class="wy-breadcrumbs-aside">
{% if hasdoc(pagename) %}
{% if display_github %}
{% if check_meta and 'github_url' in meta %}
<!-- User defined GitHub URL -->
<a href="{{ meta['github_url'] }}" class="fa fa-github"> {{ _('Edit on GitHub') }}</a>
{% else %}
<a href="https://{{ github_host|default("github.com") }}/{{ github_user }}/{{ github_repo }}/{{ theme_vcs_pageview_mode|default("blob") }}/{{ github_version }}{{ conf_py_path }}{{ pagename }}{{ suffix }}" class="fa fa-github"> {{ _('Edit on GitHub') }}</a>
{% endif %}
{% elif display_bitbucket %}
{% if check_meta and 'bitbucket_url' in meta %}
<!-- User defined Bitbucket URL -->
<a href="{{ meta['bitbucket_url'] }}" class="fa fa-bitbucket"> {{ _('Edit on Bitbucket') }}</a>
{% else %}
<a href="https://bitbucket.org/{{ bitbucket_user }}/{{ bitbucket_repo }}/src/{{ bitbucket_version}}{{ conf_py_path }}{{ pagename }}{{ suffix }}?mode={{ theme_vcs_pageview_mode|default("view") }}" class="fa fa-bitbucket"> {{ _('Edit on Bitbucket') }}</a>
{% endif %}
{% elif display_gitlab %}
{% if check_meta and 'gitlab_url' in meta %}
<!-- User defined GitLab URL -->
<a href="{{ meta['gitlab_url'] }}" class="fa fa-gitlab"> {{ _('Edit on GitLab') }}</a>
{% else %}
<a href="https://{{ gitlab_host|default("gitlab.com") }}/{{ gitlab_user }}/{{ gitlab_repo }}/{{ theme_vcs_pageview_mode|default("blob") }}/{{ gitlab_version }}{{ conf_py_path }}{{ pagename }}{{ suffix }}" class="fa fa-gitlab"> {{ _('Edit on GitLab') }}</a>
{% endif %}
{% elif show_source and source_url_prefix %}
<a href="{{ source_url_prefix }}{{ pagename }}{{ suffix }}">{{ _('View page source') }}</a>
{% elif show_source and has_source and sourcename %}
<a href="{{ pathto('_sources/' + sourcename, true)|e }}" rel="nofollow"> {{ _('View page source') }}</a>
{% endif %}
{% endif %}
</li>
{% endblock %}
</ul>
{% if (theme_prev_next_buttons_location == 'top' or theme_prev_next_buttons_location == 'both') and (next or prev) %}
<div class="rst-breadcrumbs-buttons" role="navigation" aria-label="breadcrumb navigation">
{% if next %}
<a href="{{ next.link|e }}" class="btn btn-neutral float-right" title="{{ next.title|striptags|e }}" accesskey="n">{{ _('Next') }} <span class="fa fa-arrow-circle-right"></span></a>
{% endif %}
{% if prev %}
<a href="{{ prev.link|e }}" class="btn btn-neutral float-left" title="{{ prev.title|striptags|e }}" accesskey="p"><span class="fa fa-arrow-circle-left"></span> {{ _('Previous') }}</a>
{% endif %}
</div>
{% endif %}
<hr/>
</div>

View File

@@ -0,0 +1,56 @@
<footer>
{% if (theme_prev_next_buttons_location == 'bottom' or theme_prev_next_buttons_location == 'both') and (next or prev) %}
<div class="rst-footer-buttons" role="navigation" aria-label="footer navigation">
{% if next %}
<a href="{{ next.link|e }}" class="btn btn-neutral float-right" title="{{ next.title|striptags|e }}" accesskey="n" rel="next">{{ _('Next') }} <span class="fa fa-arrow-circle-right"></span></a>
{% endif %}
{% if prev %}
<a href="{{ prev.link|e }}" class="btn btn-neutral float-left" title="{{ prev.title|striptags|e }}" accesskey="p" rel="prev"><span class="fa fa-arrow-circle-left"></span> {{ _('Previous') }}</a>
{% endif %}
</div>
{% endif %}
<hr/>
<div role="contentinfo">
<p>
{%- if show_copyright %}
{%- if hasdoc('copyright') %}
{% set path = pathto('copyright') %}
{% set copyright = copyright|e %}
&copy; <a href="{{ path }}">{% trans %}Copyright{% endtrans %}</a> {{ copyright }}
{%- else %}
{% set copyright = copyright|e %}
&copy; {% trans %}Copyright{% endtrans %} {{ copyright }}
{%- endif %}
{%- endif %}
{%- if build_id and build_url %}
<span class="build">
{# Translators: Build is a noun, not a verb #}
{% trans %}Build{% endtrans %}
<a href="{{ build_url }}">{{ build_id }}</a>.
</span>
{%- elif commit %}
<span class="commit">
{% trans %}Revision{% endtrans %} <code>{{ commit }}</code>.
</span>
{%- elif last_updated %}
<span class="lastupdated">
{% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %}
</span>
{%- endif %}
</p>
</div>
{%- if show_sphinx %}
{% set sphinx_web = '<a href="http://sphinx-doc.org/">Sphinx</a>' %}
{% set readthedocs_web = '<a href="https://readthedocs.org">Read the Docs</a>' %}
{% trans sphinx_web=sphinx_web, readthedocs_web=readthedocs_web %}Built with {{ sphinx_web }} using a{% endtrans %} <a href="https://github.com/rtfd/sphinx_rtd_theme">{% trans %}theme{% endtrans %}</a> {% trans %}provided by {{ readthedocs_web }}{% endtrans %}.
{%- endif %}
{%- block extrafooter %} {% endblock %}
</footer>

View File

@@ -0,0 +1,240 @@
{# TEMPLATE VAR SETTINGS #}
{%- set url_root = pathto('', 1) %}
{%- if url_root == '#' %}{% set url_root = '' %}{% endif %}
{%- if not embedded and docstitle %}
{%- set titlesuffix = " &mdash; "|safe + docstitle|e %}
{%- else %}
{%- set titlesuffix = "" %}
{%- endif %}
{%- set lang_attr = 'en' if language == None else (language | replace('_', '-')) %}
<!DOCTYPE html>
<html lang="{{ lang_attr }}" >
<head>
<meta charset="utf-8">
{{ metatags }}
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{% block htmltitle %}
<title>{{ title|striptags|e }}{{ titlesuffix }}</title>
{% endblock %}
{# CSS #}
<link rel="stylesheet" href="{{ pathto('_static/' + style, 1) }}" type="text/css" />
<link rel="stylesheet" href="{{ pathto('_static/pygments.css', 1) }}" type="text/css" />
{%- for css in css_files %}
{%- if css|attr("rel") %}
<link rel="{{ css.rel }}" href="{{ pathto(css.filename, 1) }}" type="text/css"{% if css.title is not none %} title="{{ css.title }}"{% endif %} />
{%- else %}
<link rel="stylesheet" href="{{ pathto(css, 1) }}" type="text/css" />
{%- endif %}
{%- endfor %}
{%- for cssfile in extra_css_files %}
<link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" />
{%- endfor %}
{# FAVICON #}
{% if favicon %}
<link rel="shortcut icon" href="{{ pathto('_static/' + favicon, 1) }}"/>
{% endif %}
{# CANONICAL URL #}
{% if theme_canonical_url %}
<link rel="canonical" href="{{ theme_canonical_url }}{{ pagename }}.html"/>
{% endif %}
{# JAVASCRIPTS #}
{%- block scripts %}
<!--[if lt IE 9]>
<script src="{{ pathto('_static/js/html5shiv.min.js', 1) }}"></script>
<![endif]-->
{%- if not embedded %}
{# XXX Sphinx 1.8.0 made this an external js-file, quick fix until we refactor the template to inherert more blocks directly from sphinx #}
{% if sphinx_version >= "1.8.0" %}
<script type="text/javascript" id="documentation_options" data-url_root="{{ pathto('', 1) }}" src="{{ pathto('_static/documentation_options.js', 1) }}"></script>
{%- for scriptfile in script_files %}
{{ js_tag(scriptfile) }}
{%- endfor %}
{% else %}
<script type="text/javascript">
var DOCUMENTATION_OPTIONS = {
URL_ROOT:'{{ url_root }}',
VERSION:'{{ release|e }}',
LANGUAGE:'{{ language }}',
COLLAPSE_INDEX:false,
FILE_SUFFIX:'{{ '' if no_search_suffix else file_suffix }}',
HAS_SOURCE: {{ has_source|lower }},
SOURCELINK_SUFFIX: '{{ sourcelink_suffix }}'
};
</script>
{%- for scriptfile in script_files %}
<script type="text/javascript" src="{{ pathto(scriptfile, 1) }}"></script>
{%- endfor %}
{% endif %}
<script type="text/javascript" src="{{ pathto('_static/js/theme.js', 1) }}"></script>
{# OPENSEARCH #}
{%- if use_opensearch %}
<link rel="search" type="application/opensearchdescription+xml"
title="{% trans docstitle=docstitle|e %}Search within {{ docstitle }}{% endtrans %}"
href="{{ pathto('_static/opensearch.xml', 1) }}"/>
{%- endif %}
{%- endif %}
{%- endblock %}
{%- block linktags %}
{%- if hasdoc('about') %}
<link rel="author" title="{{ _('About these documents') }}" href="{{ pathto('about') }}" />
{%- endif %}
{%- if hasdoc('genindex') %}
<link rel="index" title="{{ _('Index') }}" href="{{ pathto('genindex') }}" />
{%- endif %}
{%- if hasdoc('search') %}
<link rel="search" title="{{ _('Search') }}" href="{{ pathto('search') }}" />
{%- endif %}
{%- if hasdoc('copyright') %}
<link rel="copyright" title="{{ _('Copyright') }}" href="{{ pathto('copyright') }}" />
{%- endif %}
{%- if next %}
<link rel="next" title="{{ next.title|striptags|e }}" href="{{ next.link|e }}" />
{%- endif %}
{%- if prev %}
<link rel="prev" title="{{ prev.title|striptags|e }}" href="{{ prev.link|e }}" />
{%- endif %}
{%- endblock %}
{%- block extrahead %} {% endblock %}
</head>
<body class="wy-body-for-nav">
{% block extrabody %} {% endblock %}
<div class="wy-grid-for-nav">
{# SIDE NAV, TOGGLES ON MOBILE #}
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
<div class="wy-side-scroll">
<div class="wy-side-nav-search" {% if theme_style_nav_header_background %} style="background: {{theme_style_nav_header_background}}" {% endif %}>
{% block sidebartitle %}
{% if logo and theme_logo_only %}
<a href="{{ pathto(master_doc) }}">
{% else %}
<a href="{{ pathto(master_doc) }}" class="icon icon-home" alt="{{ _("Documentation Home") }}"> {{ project }}
{% endif %}
{% if logo %}
{# Not strictly valid HTML, but it's the only way to display/scale
it properly, without weird scripting or heaps of work
#}
<img src="{{ pathto('_static/' + logo, 1) }}" class="logo" alt="{{ _('Logo') }}"/>
{% endif %}
</a>
{% if theme_display_version %}
{%- set nav_version = version %}
{% if READTHEDOCS and current_version %}
{%- set nav_version = current_version %}
{% endif %}
{% if nav_version %}
<div class="version">
{{ nav_version }}
</div>
{% endif %}
{% endif %}
{% include "searchbox.html" %}
{% endblock %}
</div>
{% block navigation %}
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
{% block menu %}
{#
The singlehtml builder doesn't handle this toctree call when the
toctree is empty. Skip building this for now.
#}
{% if 'singlehtml' not in builder %}
{% set global_toc = toctree(maxdepth=theme_navigation_depth|int,
collapse=theme_collapse_navigation|tobool,
includehidden=theme_includehidden|tobool,
titles_only=theme_titles_only|tobool) %}
{% endif %}
{% if global_toc %}
{{ global_toc }}
{% else %}
<!-- Local TOC -->
<div class="local-toc">{{ toc }}</div>
{% endif %}
{% endblock %}
</div>
{% endblock %}
</div>
</nav>
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
{# MOBILE NAV, TRIGGLES SIDE NAV ON TOGGLE #}
<nav class="wy-nav-top" aria-label="top navigation">
{% block mobile_nav %}
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
<a href="{{ pathto(master_doc) }}">{{ project }}</a>
{% endblock %}
</nav>
<div class="wy-nav-content">
{%- block content %}
{% if theme_style_external_links|tobool %}
<div class="rst-content style-external-links">
{% else %}
<div class="rst-content">
{% endif %}
{% include "breadcrumbs.html" %}
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
{%- block document %}
<div itemprop="articleBody">
{% block body %}{% endblock %}
</div>
{% if self.comments()|trim %}
<div class="articleComments">
{% block comments %}{% endblock %}
</div>
{% endif%}
</div>
{%- endblock %}
{% include "footer.html" %}
</div>
{%- endblock %}
</div>
</section>
</div>
{% include "versions.html" %}
<script type="text/javascript">
jQuery(function () {
SphinxRtdTheme.Navigation.enable({{ 'true' if theme_sticky_navigation|tobool else 'false' }});
});
</script>
{# Do not conflict with RTD insertion of analytics script #}
{% if not READTHEDOCS %}
{% if theme_analytics_id %}
<!-- Theme Analytics -->
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', '{{ theme_analytics_id }}', 'auto');
ga('send', 'pageview');
</script>
{% endif %}
{% endif %}
{%- block footer %} {% endblock %}
</body>
</html>

Binary file not shown.

View File

@@ -0,0 +1,208 @@
# German translations for sphinx_rtd_theme.
# Copyright (C) 2018 Read the Docs
# This file is distributed under the same license as the sphinx_rtd_theme
# project.
# Dennis Wegner <dennis@instant-thinking.de>, 2018.
msgid ""
msgstr ""
"Project-Id-Version: sphinx_rtd_theme 0.2.4\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2019-07-24 23:51-0600\n"
"PO-Revision-Date: 2020-01-30 12:53+0100\n"
"Last-Translator: Jan Niklas Hasse <jhasse@bixense.com>\n"
"Language-Team: German Team\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"Generated-By: Babel 2.4.0\n"
"X-Generator: Poedit 2.2.4\n"
#: sphinx_rtd_theme/breadcrumbs.html:31
msgid "Docs"
msgstr "Dokumente"
#: sphinx_rtd_theme/breadcrumbs.html:43 sphinx_rtd_theme/breadcrumbs.html:45
msgid "Edit on GitHub"
msgstr "Auf GitHub bearbeiten"
#: sphinx_rtd_theme/breadcrumbs.html:50 sphinx_rtd_theme/breadcrumbs.html:52
msgid "Edit on Bitbucket"
msgstr "Auf Bitbucket bearbeiten"
#: sphinx_rtd_theme/breadcrumbs.html:57 sphinx_rtd_theme/breadcrumbs.html:59
msgid "Edit on GitLab"
msgstr "Auf GitLab bearbeiten"
#: sphinx_rtd_theme/breadcrumbs.html:62 sphinx_rtd_theme/breadcrumbs.html:64
msgid "View page source"
msgstr "Seitenquelltext anzeigen"
#: sphinx_rtd_theme/breadcrumbs.html:74 sphinx_rtd_theme/footer.html:5
msgid "Next"
msgstr "Weiter"
#: sphinx_rtd_theme/breadcrumbs.html:77 sphinx_rtd_theme/footer.html:8
msgid "Previous"
msgstr "Zurück"
#: sphinx_rtd_theme/footer.html:21 sphinx_rtd_theme/footer.html:24
#: sphinx_rtd_theme/layout.html:92
msgid "Copyright"
msgstr "Copyright"
#. Build is a noun, not a verb
#: sphinx_rtd_theme/footer.html:31
msgid "Build"
msgstr "Build"
#: sphinx_rtd_theme/footer.html:36
msgid "Revision"
msgstr "Revision"
#: sphinx_rtd_theme/footer.html:40
#, python-format
msgid "Last updated on %(last_updated)s."
msgstr "Zuletzt aktualisiert am %(last_updated)s."
#: sphinx_rtd_theme/footer.html:50
#, python-format
msgid "Built with %(sphinx_web)s using a"
msgstr "Erstellt mit %(sphinx_web)s unter Verwendung eines"
#: sphinx_rtd_theme/footer.html:50
msgid "theme"
msgstr "Themes"
#: sphinx_rtd_theme/footer.html:50
#, python-format
msgid "provided by %(readthedocs_web)s"
msgstr "von %(readthedocs_web)s"
#: sphinx_rtd_theme/layout.html:61
#, python-format
msgid "Search within %(docstitle)s"
msgstr "Suche in %(docstitle)s"
#: sphinx_rtd_theme/layout.html:83
msgid "About these documents"
msgstr "Über diese Dokumente"
#: sphinx_rtd_theme/layout.html:86
msgid "Index"
msgstr "Index"
#: sphinx_rtd_theme/layout.html:89 sphinx_rtd_theme/search.html:11
msgid "Search"
msgstr "Suche"
#: sphinx_rtd_theme/layout.html:124
msgid "Logo"
msgstr "Logo"
#: sphinx_rtd_theme/search.html:26
msgid "Please activate JavaScript to enable the search functionality."
msgstr "Bitte JavaScript aktivieren um die Suchfunktion zu ermöglichen."
#. Search is a noun, not a verb
#: sphinx_rtd_theme/search.html:34
msgid "Search Results"
msgstr "Suchergebnisse"
#: sphinx_rtd_theme/search.html:36
msgid ""
"Your search did not match any documents. Please make sure that all words are "
"spelled correctly and that you've selected enough categories."
msgstr ""
"Deine Suche ergab keine Treffer. Bitte stelle sicher, dass alle Wörter "
"richtig geschrieben sind und du genug Kategorien ausgewählt hast."
#: sphinx_rtd_theme/searchbox.html:4
msgid "Search docs"
msgstr "Dokumentation durchsuchen"
#: sphinx_rtd_theme/versions.html:11
msgid "Versions"
msgstr "Versionen"
#: sphinx_rtd_theme/versions.html:17
msgid "Downloads"
msgstr "Downloads"
#. The phrase "Read the Docs" is not translated
#: sphinx_rtd_theme/versions.html:24
msgid "On Read the Docs"
msgstr "Auf Read the Docs"
#: sphinx_rtd_theme/versions.html:26
msgid "Project Home"
msgstr "Projektseite"
#: sphinx_rtd_theme/versions.html:29
msgid "Builds"
msgstr "Builds"
#: sphinx_rtd_theme/versions.html:33
msgid "Free document hosting provided by"
msgstr "Kostenloses Dokumenten-Hosting von"
#~ msgid "&copy; <a href=\"%(path)s\">Copyright</a> %(copyright)s."
#~ msgstr "&copy; <a href=\\\"%(path)s\\\">Copyright</a> %(copyright)s."
#~ msgid "&copy; Copyright %(copyright)s."
#~ msgstr "&copy; Copyright %(copyright)s."
#~ msgid ""
#~ "\n"
#~ " <span class=\"build\">\n"
#~ " Build\n"
#~ " <a href=\"%(build_url)s\">%(build_id)s</a>.\n"
#~ " </span>\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " <span class=\"build\">\n"
#~ " Build\n"
#~ " <a href=\"%(build_url)s\">%(build_id)s</a>.\n"
#~ " </span>\n"
#~ " "
#~ msgid ""
#~ "\n"
#~ " <span class=\"commit\">\n"
#~ " Revision <code>%(commit)s</code>.\n"
#~ " </span>\n"
#~ " "
#~ msgstr ""
#~ "\n"
#~ " <span class=\"commit\">\n"
#~ " Revision <code>%(commit)s</code>.\n"
#~ " </span>\n"
#~ " "
#~ msgid ""
#~ "Built with <a href=\"http://sphinx-doc.org/\">Sphinx</a> using a <a href="
#~ "\"https://github.com/snide/sphinx_rtd_theme\">theme</a> provided by <a "
#~ "href=\"https://readthedocs.org\">Read the Docs</a>"
#~ msgstr ""
#~ "Erstellt mit <a href=\"http://sphinx-doc.org/\">Sphinx</a> unter "
#~ "Verwendung eines <a href=\"https://github.com/snide/sphinx_rtd_theme"
#~ "\">Themes</a> von <a href=\"https://readthedocs.org\">Read the Docs</a>"
#~ msgid "Navigation"
#~ msgstr "Navigation"
#~ msgid ""
#~ "Created using <a href=\"http://sphinx-doc.org/\">Sphinx</a> "
#~ "%(sphinx_version)s."
#~ msgstr ""
#~ "Erstellt mit <a href=\"http://sphinx-doc.org/\">Sphinx</a> "
#~ "%(sphinx_version)s."
#~ msgid ""
#~ "Free document hosting provided by <a href=\"http://www.readthedocs.org"
#~ "\">Read the Docs</a>."
#~ msgstr ""
#~ "Dokumentationshosting gratis bei <a href=\"http://www.readthedocs.org"
#~ "\">Read the Docs</a>."

Binary file not shown.

View File

@@ -0,0 +1,147 @@
# English translations for sphinx_rtd_theme.
# Copyright (C) 2019 ORGANIZATION
# This file is distributed under the same license as the sphinx_rtd_theme
# project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2019.
#
msgid ""
msgstr ""
"Project-Id-Version: sphinx_rtd_theme 0.4.3.dev0\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2019-07-24 23:51-0600\n"
"PO-Revision-Date: 2019-07-16 15:43-0600\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: en\n"
"Language-Team: en <LL@li.org>\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.7.0\n"
#: sphinx_rtd_theme/breadcrumbs.html:31
msgid "Docs"
msgstr ""
#: sphinx_rtd_theme/breadcrumbs.html:43 sphinx_rtd_theme/breadcrumbs.html:45
msgid "Edit on GitHub"
msgstr ""
#: sphinx_rtd_theme/breadcrumbs.html:50 sphinx_rtd_theme/breadcrumbs.html:52
msgid "Edit on Bitbucket"
msgstr ""
#: sphinx_rtd_theme/breadcrumbs.html:57 sphinx_rtd_theme/breadcrumbs.html:59
msgid "Edit on GitLab"
msgstr ""
#: sphinx_rtd_theme/breadcrumbs.html:62 sphinx_rtd_theme/breadcrumbs.html:64
msgid "View page source"
msgstr ""
#: sphinx_rtd_theme/breadcrumbs.html:74 sphinx_rtd_theme/footer.html:5
msgid "Next"
msgstr ""
#: sphinx_rtd_theme/breadcrumbs.html:77 sphinx_rtd_theme/footer.html:8
msgid "Previous"
msgstr ""
#: sphinx_rtd_theme/footer.html:21 sphinx_rtd_theme/footer.html:24
#: sphinx_rtd_theme/layout.html:92
msgid "Copyright"
msgstr ""
#. Build is a noun, not a verb
#: sphinx_rtd_theme/footer.html:31
msgid "Build"
msgstr ""
#: sphinx_rtd_theme/footer.html:36
msgid "Revision"
msgstr ""
#: sphinx_rtd_theme/footer.html:40
#, python-format
msgid "Last updated on %(last_updated)s."
msgstr ""
#: sphinx_rtd_theme/footer.html:50
#, python-format
msgid "Built with %(sphinx_web)s using a"
msgstr ""
#: sphinx_rtd_theme/footer.html:50
msgid "theme"
msgstr ""
#: sphinx_rtd_theme/footer.html:50
#, python-format
msgid "provided by %(readthedocs_web)s"
msgstr ""
#: sphinx_rtd_theme/layout.html:61
#, python-format
msgid "Search within %(docstitle)s"
msgstr ""
#: sphinx_rtd_theme/layout.html:83
msgid "About these documents"
msgstr ""
#: sphinx_rtd_theme/layout.html:86
msgid "Index"
msgstr ""
#: sphinx_rtd_theme/layout.html:89 sphinx_rtd_theme/search.html:11
msgid "Search"
msgstr ""
#: sphinx_rtd_theme/layout.html:124
msgid "Logo"
msgstr ""
#: sphinx_rtd_theme/search.html:26
msgid "Please activate JavaScript to enable the search functionality."
msgstr ""
#. Search is a noun, not a verb
#: sphinx_rtd_theme/search.html:34
msgid "Search Results"
msgstr ""
#: sphinx_rtd_theme/search.html:36
msgid ""
"Your search did not match any documents. Please make sure that all words "
"are spelled correctly and that you've selected enough categories."
msgstr ""
#: sphinx_rtd_theme/searchbox.html:4
msgid "Search docs"
msgstr ""
#: sphinx_rtd_theme/versions.html:11
msgid "Versions"
msgstr ""
#: sphinx_rtd_theme/versions.html:17
msgid "Downloads"
msgstr ""
#. The phrase "Read the Docs" is not translated
#: sphinx_rtd_theme/versions.html:24
msgid "On Read the Docs"
msgstr ""
#: sphinx_rtd_theme/versions.html:26
msgid "Project Home"
msgstr ""
#: sphinx_rtd_theme/versions.html:29
msgid "Builds"
msgstr ""
#: sphinx_rtd_theme/versions.html:33
msgid "Free document hosting provided by"
msgstr ""

Binary file not shown.

View File

@@ -0,0 +1,149 @@
# Spanish translations for sphinx_rtd_theme.
# Copyright (C) 2019 Read the Docs, Inc
# This file is distributed under the same license as the sphinx_rtd_theme
# project.
msgid ""
msgstr ""
"Project-Id-Version: sphinx_rtd_theme 0.4.3.dev0\n"
"Report-Msgid-Bugs-To: support@readthedocs.org\n"
"POT-Creation-Date: 2019-07-24 23:51-0600\n"
"PO-Revision-Date: 2019-07-16 21:44+0000\n"
"Last-Translator: Anthony <aj@ohess.org>, 2019\n"
"Language: es\n"
"Language-Team: Spanish "
"(https://www.transifex.com/readthedocs/teams/101354/es/)\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.7.0\n"
#: sphinx_rtd_theme/breadcrumbs.html:31
msgid "Docs"
msgstr "Documentos"
#: sphinx_rtd_theme/breadcrumbs.html:43 sphinx_rtd_theme/breadcrumbs.html:45
msgid "Edit on GitHub"
msgstr "Editar en GitHub"
#: sphinx_rtd_theme/breadcrumbs.html:50 sphinx_rtd_theme/breadcrumbs.html:52
msgid "Edit on Bitbucket"
msgstr "Editar en Bitbucket"
#: sphinx_rtd_theme/breadcrumbs.html:57 sphinx_rtd_theme/breadcrumbs.html:59
msgid "Edit on GitLab"
msgstr "Editar en GitLab"
#: sphinx_rtd_theme/breadcrumbs.html:62 sphinx_rtd_theme/breadcrumbs.html:64
msgid "View page source"
msgstr "Ver código fuente de la página"
#: sphinx_rtd_theme/breadcrumbs.html:74 sphinx_rtd_theme/footer.html:5
msgid "Next"
msgstr "Siguiente"
#: sphinx_rtd_theme/breadcrumbs.html:77 sphinx_rtd_theme/footer.html:8
msgid "Previous"
msgstr "Anterior"
#: sphinx_rtd_theme/footer.html:21 sphinx_rtd_theme/footer.html:24
#: sphinx_rtd_theme/layout.html:92
msgid "Copyright"
msgstr "Derechos de autor"
#. Build is a noun, not a verb
#: sphinx_rtd_theme/footer.html:31
msgid "Build"
msgstr "Construido"
#: sphinx_rtd_theme/footer.html:36
msgid "Revision"
msgstr "Revisión"
#: sphinx_rtd_theme/footer.html:40
#, python-format
msgid "Last updated on %(last_updated)s."
msgstr "Actualizado por última vez en %(last_updated)s."
#: sphinx_rtd_theme/footer.html:50
#, python-format
msgid "Built with %(sphinx_web)s using a"
msgstr "Construido con %(sphinx_web)s usando un"
#: sphinx_rtd_theme/footer.html:50
msgid "theme"
msgstr "tema"
#: sphinx_rtd_theme/footer.html:50
#, python-format
msgid "provided by %(readthedocs_web)s"
msgstr "proporcionado por %(readthedocs_web)s"
#: sphinx_rtd_theme/layout.html:61
#, python-format
msgid "Search within %(docstitle)s"
msgstr "Buscar en %(docstitle)s"
#: sphinx_rtd_theme/layout.html:83
msgid "About these documents"
msgstr "Sobre esta documentación"
#: sphinx_rtd_theme/layout.html:86
msgid "Index"
msgstr "Índice"
#: sphinx_rtd_theme/layout.html:89 sphinx_rtd_theme/search.html:11
msgid "Search"
msgstr "Búsqueda"
#: sphinx_rtd_theme/layout.html:124
msgid "Logo"
msgstr "Logotipo"
#: sphinx_rtd_theme/search.html:26
msgid "Please activate JavaScript to enable the search functionality."
msgstr "Por favor, active JavaScript para habilitar la funcionalidad de búsqueda."
#. Search is a noun, not a verb
#: sphinx_rtd_theme/search.html:34
msgid "Search Results"
msgstr "Resultados de la búsqueda"
#: sphinx_rtd_theme/search.html:36
msgid ""
"Your search did not match any documents. Please make sure that all words "
"are spelled correctly and that you've selected enough categories."
msgstr ""
"Su búsqueda no coincide con ningún documento. Por favor, asegúrese de que"
" todas las palabras estén correctamente escritas y que usted haya "
"seleccionado las suficientes categorías."
#: sphinx_rtd_theme/searchbox.html:4
msgid "Search docs"
msgstr "Buscar documentos"
#: sphinx_rtd_theme/versions.html:11
msgid "Versions"
msgstr "Versiones"
#: sphinx_rtd_theme/versions.html:17
msgid "Downloads"
msgstr "Descargas"
#. The phrase "Read the Docs" is not translated
#: sphinx_rtd_theme/versions.html:24
msgid "On Read the Docs"
msgstr "En Read the Docs"
#: sphinx_rtd_theme/versions.html:26
msgid "Project Home"
msgstr "Página de Proyecto"
#: sphinx_rtd_theme/versions.html:29
msgid "Builds"
msgstr "Construcciones"
#: sphinx_rtd_theme/versions.html:33
msgid "Free document hosting provided by"
msgstr "Alojamiento gratuito de documentos proporcionado por"

Binary file not shown.

View File

@@ -0,0 +1,152 @@
# English translations for sphinx_rtd_theme.
# Copyright (C) 2019 ORGANIZATION
# This file is distributed under the same license as the sphinx_rtd_theme
# project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2019.
#
# Translators:
# Jesse Tan, 2019
msgid ""
msgstr ""
"Project-Id-Version: sphinx_rtd_theme 0.4.3.dev0\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2019-07-24 23:51-0600\n"
"PO-Revision-Date: 2019-07-16 21:44+0000\n"
"Last-Translator: Jesse Tan, 2019\n"
"Language: nl\n"
"Language-Team: Dutch "
"(https://www.transifex.com/readthedocs/teams/101354/nl/)\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.7.0\n"
#: sphinx_rtd_theme/breadcrumbs.html:31
msgid "Docs"
msgstr "Documentatie"
#: sphinx_rtd_theme/breadcrumbs.html:43 sphinx_rtd_theme/breadcrumbs.html:45
msgid "Edit on GitHub"
msgstr "Bewerk op GitHub"
#: sphinx_rtd_theme/breadcrumbs.html:50 sphinx_rtd_theme/breadcrumbs.html:52
msgid "Edit on Bitbucket"
msgstr "Bewerk op BitBucket"
#: sphinx_rtd_theme/breadcrumbs.html:57 sphinx_rtd_theme/breadcrumbs.html:59
msgid "Edit on GitLab"
msgstr "Bewerk op GitLab"
#: sphinx_rtd_theme/breadcrumbs.html:62 sphinx_rtd_theme/breadcrumbs.html:64
msgid "View page source"
msgstr "Bekijk paginabron"
#: sphinx_rtd_theme/breadcrumbs.html:74 sphinx_rtd_theme/footer.html:5
msgid "Next"
msgstr "Volgende"
#: sphinx_rtd_theme/breadcrumbs.html:77 sphinx_rtd_theme/footer.html:8
msgid "Previous"
msgstr "Vorige"
#: sphinx_rtd_theme/footer.html:21 sphinx_rtd_theme/footer.html:24
#: sphinx_rtd_theme/layout.html:92
msgid "Copyright"
msgstr "Copyright"
#. Build is a noun, not a verb
#: sphinx_rtd_theme/footer.html:31
msgid "Build"
msgstr "Bouwsel"
#: sphinx_rtd_theme/footer.html:36
msgid "Revision"
msgstr "Revisie"
#: sphinx_rtd_theme/footer.html:40
#, python-format
msgid "Last updated on %(last_updated)s."
msgstr "Laatste update op %(last_updated)s."
#: sphinx_rtd_theme/footer.html:50
#, python-format
msgid "Built with %(sphinx_web)s using a"
msgstr "Gebouwd met %(sphinx_web)s met een"
#: sphinx_rtd_theme/footer.html:50
msgid "theme"
msgstr "thema"
#: sphinx_rtd_theme/footer.html:50
#, python-format
msgid "provided by %(readthedocs_web)s"
msgstr "geleverd door %(readthedocs_web)s"
#: sphinx_rtd_theme/layout.html:61
#, python-format
msgid "Search within %(docstitle)s"
msgstr "Zoek binnen %(docstitle)s"
#: sphinx_rtd_theme/layout.html:83
msgid "About these documents"
msgstr "Over deze documenten"
#: sphinx_rtd_theme/layout.html:86
msgid "Index"
msgstr "Index"
#: sphinx_rtd_theme/layout.html:89 sphinx_rtd_theme/search.html:11
msgid "Search"
msgstr "Zoek"
#: sphinx_rtd_theme/layout.html:124
msgid "Logo"
msgstr "Logo"
#: sphinx_rtd_theme/search.html:26
msgid "Please activate JavaScript to enable the search functionality."
msgstr "Zet JavaScript aan om de zoekfunctie mogelijk te maken."
#. Search is a noun, not a verb
#: sphinx_rtd_theme/search.html:34
msgid "Search Results"
msgstr "Zoekresultaten"
#: sphinx_rtd_theme/search.html:36
msgid ""
"Your search did not match any documents. Please make sure that all words "
"are spelled correctly and that you've selected enough categories."
msgstr ""
"Zoekpoging vond geen documenten. Zorg ervoor dat alle woorden correct "
"zijn gespeld en dat voldoende categorieën zijn geselecteerd."
#: sphinx_rtd_theme/searchbox.html:4
msgid "Search docs"
msgstr "Zoek in documentatie"
#: sphinx_rtd_theme/versions.html:11
msgid "Versions"
msgstr "Versies"
#: sphinx_rtd_theme/versions.html:17
msgid "Downloads"
msgstr "Downloads"
#. The phrase "Read the Docs" is not translated
#: sphinx_rtd_theme/versions.html:24
msgid "On Read the Docs"
msgstr "Op Read the Docs"
#: sphinx_rtd_theme/versions.html:26
msgid "Project Home"
msgstr "Project Home"
#: sphinx_rtd_theme/versions.html:29
msgid "Builds"
msgstr "Bouwsels"
#: sphinx_rtd_theme/versions.html:33
msgid "Free document hosting provided by"
msgstr "Gratis hosting voor documentatie verzorgd door"

Binary file not shown.

View File

@@ -0,0 +1,148 @@
# Russian translations for sphinx_rtd_theme.
# Copyright (C) 2019 Read the Docs, Inc
# This file is distributed under the same license as the sphinx_rtd_theme
# project.
msgid ""
msgstr ""
"Project-Id-Version: sphinx_rtd_theme 0.4.3.dev0\n"
"Report-Msgid-Bugs-To: support@readthedocs.org\n"
"POT-Creation-Date: 2019-07-24 23:51-0600\n"
"PO-Revision-Date: 2019-07-16 21:44+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: ru\n"
"Language-Team: Russian "
"(https://www.transifex.com/readthedocs/teams/101354/ru/)\n"
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) "
"|| (n%100>=11 && n%100<=14)? 2 : 3)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.7.0\n"
#: sphinx_rtd_theme/breadcrumbs.html:31
msgid "Docs"
msgstr ""
#: sphinx_rtd_theme/breadcrumbs.html:43 sphinx_rtd_theme/breadcrumbs.html:45
msgid "Edit on GitHub"
msgstr ""
#: sphinx_rtd_theme/breadcrumbs.html:50 sphinx_rtd_theme/breadcrumbs.html:52
msgid "Edit on Bitbucket"
msgstr ""
#: sphinx_rtd_theme/breadcrumbs.html:57 sphinx_rtd_theme/breadcrumbs.html:59
msgid "Edit on GitLab"
msgstr ""
#: sphinx_rtd_theme/breadcrumbs.html:62 sphinx_rtd_theme/breadcrumbs.html:64
msgid "View page source"
msgstr ""
#: sphinx_rtd_theme/breadcrumbs.html:74 sphinx_rtd_theme/footer.html:5
msgid "Next"
msgstr ""
#: sphinx_rtd_theme/breadcrumbs.html:77 sphinx_rtd_theme/footer.html:8
msgid "Previous"
msgstr ""
#: sphinx_rtd_theme/footer.html:21 sphinx_rtd_theme/footer.html:24
#: sphinx_rtd_theme/layout.html:92
msgid "Copyright"
msgstr ""
#. Build is a noun, not a verb
#: sphinx_rtd_theme/footer.html:31
msgid "Build"
msgstr ""
#: sphinx_rtd_theme/footer.html:36
msgid "Revision"
msgstr ""
#: sphinx_rtd_theme/footer.html:40
#, python-format
msgid "Last updated on %(last_updated)s."
msgstr ""
#: sphinx_rtd_theme/footer.html:50
#, python-format
msgid "Built with %(sphinx_web)s using a"
msgstr ""
#: sphinx_rtd_theme/footer.html:50
msgid "theme"
msgstr ""
#: sphinx_rtd_theme/footer.html:50
#, python-format
msgid "provided by %(readthedocs_web)s"
msgstr ""
#: sphinx_rtd_theme/layout.html:61
#, python-format
msgid "Search within %(docstitle)s"
msgstr ""
#: sphinx_rtd_theme/layout.html:83
msgid "About these documents"
msgstr ""
#: sphinx_rtd_theme/layout.html:86
msgid "Index"
msgstr ""
#: sphinx_rtd_theme/layout.html:89 sphinx_rtd_theme/search.html:11
msgid "Search"
msgstr ""
#: sphinx_rtd_theme/layout.html:124
msgid "Logo"
msgstr ""
#: sphinx_rtd_theme/search.html:26
msgid "Please activate JavaScript to enable the search functionality."
msgstr ""
#. Search is a noun, not a verb
#: sphinx_rtd_theme/search.html:34
msgid "Search Results"
msgstr ""
#: sphinx_rtd_theme/search.html:36
msgid ""
"Your search did not match any documents. Please make sure that all words "
"are spelled correctly and that you've selected enough categories."
msgstr ""
#: sphinx_rtd_theme/searchbox.html:4
msgid "Search docs"
msgstr ""
#: sphinx_rtd_theme/versions.html:11
msgid "Versions"
msgstr ""
#: sphinx_rtd_theme/versions.html:17
msgid "Downloads"
msgstr ""
#. The phrase "Read the Docs" is not translated
#: sphinx_rtd_theme/versions.html:24
msgid "On Read the Docs"
msgstr ""
#: sphinx_rtd_theme/versions.html:26
msgid "Project Home"
msgstr ""
#: sphinx_rtd_theme/versions.html:29
msgid "Builds"
msgstr ""
#: sphinx_rtd_theme/versions.html:33
msgid "Free document hosting provided by"
msgstr ""

View File

@@ -0,0 +1,146 @@
# Translations template for sphinx_rtd_theme.
# Copyright (C) 2019 ORGANIZATION
# This file is distributed under the same license as the sphinx_rtd_theme
# project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2019.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: sphinx_rtd_theme 0.4.3.dev0\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2019-07-24 23:51-0600\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.7.0\n"
#: sphinx_rtd_theme/breadcrumbs.html:31
msgid "Docs"
msgstr ""
#: sphinx_rtd_theme/breadcrumbs.html:43 sphinx_rtd_theme/breadcrumbs.html:45
msgid "Edit on GitHub"
msgstr ""
#: sphinx_rtd_theme/breadcrumbs.html:50 sphinx_rtd_theme/breadcrumbs.html:52
msgid "Edit on Bitbucket"
msgstr ""
#: sphinx_rtd_theme/breadcrumbs.html:57 sphinx_rtd_theme/breadcrumbs.html:59
msgid "Edit on GitLab"
msgstr ""
#: sphinx_rtd_theme/breadcrumbs.html:62 sphinx_rtd_theme/breadcrumbs.html:64
msgid "View page source"
msgstr ""
#: sphinx_rtd_theme/breadcrumbs.html:74 sphinx_rtd_theme/footer.html:5
msgid "Next"
msgstr ""
#: sphinx_rtd_theme/breadcrumbs.html:77 sphinx_rtd_theme/footer.html:8
msgid "Previous"
msgstr ""
#: sphinx_rtd_theme/footer.html:21 sphinx_rtd_theme/footer.html:24
#: sphinx_rtd_theme/layout.html:92
msgid "Copyright"
msgstr ""
#. Build is a noun, not a verb
#: sphinx_rtd_theme/footer.html:31
msgid "Build"
msgstr ""
#: sphinx_rtd_theme/footer.html:36
msgid "Revision"
msgstr ""
#: sphinx_rtd_theme/footer.html:40
#, python-format
msgid "Last updated on %(last_updated)s."
msgstr ""
#: sphinx_rtd_theme/footer.html:50
#, python-format
msgid "Built with %(sphinx_web)s using a"
msgstr ""
#: sphinx_rtd_theme/footer.html:50
msgid "theme"
msgstr ""
#: sphinx_rtd_theme/footer.html:50
#, python-format
msgid "provided by %(readthedocs_web)s"
msgstr ""
#: sphinx_rtd_theme/layout.html:61
#, python-format
msgid "Search within %(docstitle)s"
msgstr ""
#: sphinx_rtd_theme/layout.html:83
msgid "About these documents"
msgstr ""
#: sphinx_rtd_theme/layout.html:86
msgid "Index"
msgstr ""
#: sphinx_rtd_theme/layout.html:89 sphinx_rtd_theme/search.html:11
msgid "Search"
msgstr ""
#: sphinx_rtd_theme/layout.html:124
msgid "Logo"
msgstr ""
#: sphinx_rtd_theme/search.html:26
msgid "Please activate JavaScript to enable the search functionality."
msgstr ""
#. Search is a noun, not a verb
#: sphinx_rtd_theme/search.html:34
msgid "Search Results"
msgstr ""
#: sphinx_rtd_theme/search.html:36
msgid ""
"Your search did not match any documents. Please make sure that all words "
"are spelled correctly and that you've selected enough categories."
msgstr ""
#: sphinx_rtd_theme/searchbox.html:4
msgid "Search docs"
msgstr ""
#: sphinx_rtd_theme/versions.html:11
msgid "Versions"
msgstr ""
#: sphinx_rtd_theme/versions.html:17
msgid "Downloads"
msgstr ""
#. The phrase "Read the Docs" is not translated
#: sphinx_rtd_theme/versions.html:24
msgid "On Read the Docs"
msgstr ""
#: sphinx_rtd_theme/versions.html:26
msgid "Project Home"
msgstr ""
#: sphinx_rtd_theme/versions.html:29
msgid "Builds"
msgstr ""
#: sphinx_rtd_theme/versions.html:33
msgid "Free document hosting provided by"
msgstr ""

View File

@@ -0,0 +1,54 @@
{#
basic/search.html
~~~~~~~~~~~~~~~~~
Template for the search page.
:copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
#}
{%- extends "layout.html" %}
{% set title = _('Search') %}
{%- block scripts %}
{{ super() }}
<script type="text/javascript" src="{{ pathto('_static/searchtools.js', 1) }}"></script>
{%- endblock %}
{% block footer %}
<script type="text/javascript">
jQuery(function() { Search.loadIndex("{{ pathto('searchindex.js', 1) }}"); });
</script>
{# this is used when loading the search index using $.ajax fails,
such as on Chrome for documents on localhost #}
<script type="text/javascript" id="searchindexloader"></script>
{{ super() }}
{% endblock %}
{% block body %}
<noscript>
<div id="fallback" class="admonition warning">
<p class="last">
{% trans trimmed %}Please activate JavaScript to enable the search
functionality.{% endtrans %}
</p>
</div>
</noscript>
{% if search_performed %}
{# Translators: Search is a noun, not a verb #}
<h2>{{ _('Search Results') }}</h2>
{% if not search_results %}
<p>{{ _('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.') }}</p>
{% endif %}
{% endif %}
<div id="search-results">
{% if search_results %}
<ul>
{% for href, caption, context in search_results %}
<li>
<a href="{{ pathto(item.href) }}">{{ caption }}</a>
<p class="context">{{ context|e }}</p>
</li>
{% endfor %}
</ul>
{% endif %}
</div>
{% endblock %}

View File

@@ -0,0 +1,9 @@
{%- if builder != 'singlehtml' %}
<div role="search">
<form id="rtd-search-form" class="wy-form" action="{{ pathto('search') }}" method="get">
<input type="text" name="q" placeholder="{{ _('Search docs') }}" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
</div>
{%- endif %}

View File

@@ -0,0 +1 @@
.fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge .rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
!function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=4)}({4:function(e,t,r){}});

View File

@@ -0,0 +1 @@
!function(n){var e={};function t(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return n[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=n,t.c=e,t.d=function(n,e,i){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:i})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var i=Object.create(null);if(t.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(i,o,function(e){return n[e]}.bind(null,o));return i},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){t(1),n.exports=t(3)},function(n,e,t){(function(){var e="undefined"!=typeof window?window.jQuery:t(2);n.exports.ThemeNav={navBar:null,win:null,winScroll:!1,winResize:!1,linkScroll:!1,winPosition:0,winHeight:null,docHeight:null,isRunning:!1,enable:function(n){var t=this;void 0===n&&(n=!0),t.isRunning||(t.isRunning=!0,e((function(e){t.init(e),t.reset(),t.win.on("hashchange",t.reset),n&&t.win.on("scroll",(function(){t.linkScroll||t.winScroll||(t.winScroll=!0,requestAnimationFrame((function(){t.onScroll()})))})),t.win.on("resize",(function(){t.winResize||(t.winResize=!0,requestAnimationFrame((function(){t.onResize()})))})),t.onResize()})))},enableSticky:function(){this.enable(!0)},init:function(n){n(document);var e=this;this.navBar=n("div.wy-side-scroll:first"),this.win=n(window),n(document).on("click","[data-toggle='wy-nav-top']",(function(){n("[data-toggle='wy-nav-shift']").toggleClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift")})).on("click",".wy-menu-vertical .current ul li a",(function(){var t=n(this);n("[data-toggle='wy-nav-shift']").removeClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift"),e.toggleCurrent(t),e.hashChange()})).on("click","[data-toggle='rst-current-version']",(function(){n("[data-toggle='rst-versions']").toggleClass("shift-up")})),n("table.docutils:not(.field-list,.footnote,.citation)").wrap("<div class='wy-table-responsive'></div>"),n("table.docutils.footnote").wrap("<div class='wy-table-responsive footnote'></div>"),n("table.docutils.citation").wrap("<div class='wy-table-responsive citation'></div>"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n('<span class="toctree-expand"></span>'),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}t.length>0&&($(".wy-menu-vertical .current").removeClass("current"),t.addClass("current"),t.closest("li.toctree-l1").addClass("current"),t.closest("li.toctree-l1").parent().addClass("current"),t.closest("li.toctree-l1").addClass("current"),t.closest("li.toctree-l2").addClass("current"),t.closest("li.toctree-l3").addClass("current"),t.closest("li.toctree-l4").addClass("current"),t[0].scrollIntoView())}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current"),e.siblings().find("li.current").removeClass("current"),e.find("> ul li.current").removeClass("current"),e.toggleClass("current")}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t<e.length&&!window.requestAnimationFrame;++t)window.requestAnimationFrame=window[e[t]+"RequestAnimationFrame"],window.cancelAnimationFrame=window[e[t]+"CancelAnimationFrame"]||window[e[t]+"CancelRequestAnimationFrame"];window.requestAnimationFrame||(window.requestAnimationFrame=function(e,t){var i=(new Date).getTime(),o=Math.max(0,16-(i-n)),r=window.setTimeout((function(){e(i+o)}),o);return n=i+o,r}),window.cancelAnimationFrame||(window.cancelAnimationFrame=function(n){clearTimeout(n)})}()}).call(window)},function(n,e){n.exports=jQuery},function(n,e,t){}]);

View File

@@ -0,0 +1,18 @@
[theme]
inherit = basic
stylesheet = css/theme.css
pygments_style = default
[options]
canonical_url =
analytics_id =
collapse_navigation = True
sticky_navigation = True
navigation_depth = 4
includehidden = True
titles_only =
logo_only =
display_version = True
prev_next_buttons_location = bottom
style_external_links = False
style_nav_header_background =

View File

@@ -0,0 +1,34 @@
{% if READTHEDOCS %}
{# Add rst-badge after rst-versions for small badge style. #}
<div class="rst-versions" data-toggle="rst-versions" role="note" aria-label="versions">
<span class="rst-current-version" data-toggle="rst-current-version">
<span class="fa fa-book"> Read the Docs</span>
v: {{ current_version }}
<span class="fa fa-caret-down"></span>
</span>
<div class="rst-other-versions">
<dl>
<dt>{{ _('Versions') }}</dt>
{% for slug, url in versions %}
<dd><a href="{{ url }}">{{ slug }}</a></dd>
{% endfor %}
</dl>
<dl>
<dt>{{ _('Downloads') }}</dt>
{% for type, url in downloads %}
<dd><a href="{{ url }}">{{ type }}</a></dd>
{% endfor %}
</dl>
<dl>
{# Translators: The phrase "Read the Docs" is not translated #}
<dt>{{ _('On Read the Docs') }}</dt>
<dd>
<a href="//{{ PRODUCTION_DOMAIN }}/projects/{{ slug }}/?fromdocs={{ slug }}">{{ _('Project Home') }}</a>
</dd>
<dd>
<a href="//{{ PRODUCTION_DOMAIN }}/builds/{{ slug }}/?fromdocs={{ slug }}">{{ _('Builds') }}</a>
</dd>
</dl>
</div>
</div>
{% endif %}

146
docs/conf.py Normal file
View File

@@ -0,0 +1,146 @@
# Configuration file for the Sphinx documentation builder.
#
# This file only contains a selection of the most common options.
# For a full list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
import subprocess, os, re
def get_version():
try:
with open('../src/CMakeLists.txt', 'r') as file:
content = file.read()
version = re.search(r"project\([^\)]+VERSION (\d+\.\d+\.\d+)[^\)]*\)", content).group(1)
return version.strip()
except Exception:
return None
# -- Project information -----------------------------------------------------
project = 'mp-units'
copyright = '2018-present, Mateusz Pusz'
author = 'Mateusz Pusz'
# The major project version, used as the replacement for |version|.
version = get_version()
# The full project version, used as the replacement for |release| and
# e.g. in the HTML templates.
release = get_version()
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autosectionlabel',
'sphinx.ext.githubpages',
'sphinx.ext.graphviz',
'recommonmark',
'breathe'
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# If true, Sphinx will warn about all references where the target cannot
# be found. Default is False.
nitpicky = True
# A list of (type, target) tuples (by default empty) that should be ignored
# when generating warnings in “nitpicky mode”. Note that type should include
# the domain name if present. Example entries would be ('py:func', 'int')
# or ('envvar', 'LD_LIBRARY_PATH').
nitpick_ignore = []
# -- C++ configuration ---------------------------------------------------
# The name of the default domain. Can also be None to disable a default
# domain. The default is 'py'.
primary_domain = 'cpp'
# The reST default role (used for this markup: `text`) to use for all documents.
default_role = 'cpp:any'
# The default language to highlight source code in. The default is 'python3'.
# The value should be a valid Pygments lexer name (https://pygments.org/docs/lexers).
highlight_language = 'cpp'
# The style name to use for Pygments highlighting of source code. If not set,
# either the themes default style or 'sphinx' is selected for HTML output.
pygments_style = 'default'
# A list of prefixes that will be ignored when sorting C++ objects in the global
# index. For example ['awesome_lib::'].
cpp_index_common_prefix = ['units::']
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'sphinx_rtd_theme'
html_theme_path = ["_themes", ]
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# If given, this must be the name of an image file (path relative to the
# configuration directory) that is the logo of the docs. It is placed at the
# top of the sidebar; its width should therefore not exceed 200 pixels.
# Default: None.
# html_logo =
# These paths are either relative to html_static_path or fully qualified
# paths (eg. https://...)
html_css_files = [
'css/custom.css'
]
# -- Breathe configuration ---------------------------------------------------
# Check if we're running on Read the Docs' servers
read_the_docs_build = os.environ.get('READTHEDOCS', None) == 'True'
# This should be a dictionary in which the keys are project names and the values
# are paths to the folder containing the doxygen output for that project.
breathe_projects = {}
if read_the_docs_build:
input_dir = '../src'
output_dir = 'build'
configureDoxyfile(input_dir, output_dir)
subprocess.call('doxygen', shell=True)
breathe_projects['mp-units'] = output_dir + '/xml'
# This should match one of the keys in the breathe_projects dictionary and
# indicates which project should be used when the project is not specified on
# the directive.
breathe_default_project = 'mp-units'
# Allows you to specify domains for particular files according to their extension.
breathe_domain_by_extension = {"h" : "cpp"}
# Provides the directive flags that should be applied to all directives which
# take :members:, :private-members: and :undoc-members: options. By default,
# this is set to an empty list, which means no members are displayed.
breathe_default_members = ('members', )
def configureDoxyfile(input_dir, output_dir):
with open('Doxyfile.in', 'r') as file:
filedata = file.read()
filedata = filedata.replace('@DOXYGEN_INPUT_DIR@', input_dir)
filedata = filedata.replace('@DOXYGEN_OUTPUT_DIR@', output_dir)
with open('Doxyfile', 'w') as file:
file.write(filedata)

BIN
docs/design.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

13
docs/design.rst Normal file
View File

@@ -0,0 +1,13 @@
Design
======
.. note::
For brevity all the code examples in this documentation will assume::
using namespace units;
.. toctree::
:maxdepth: 2
design/quantity

67
docs/design/quantity.rst Normal file
View File

@@ -0,0 +1,67 @@
.. namespace:: units
quantity
========
Interface
---------
`quantity` class template provides a similar interface to
`std::chrono::duration <https://en.cppreference.com/w/cpp/chrono/duration>`_.
The difference is that it uses ``double`` as a default representation and has
a few additional member types and functions::
template<Dimension D, UnitOf<D> U, Scalar Rep = double>
class quantity {
public:
using dimension = D;
using unit = U;
using rep = Rep;
[[nodiscard]] static constexpr quantity one() noexcept;
// ...
};
template<typename D1, typename U1, typename Rep1, typename D2, typename U2, typename Rep2>
requires detail::basic_arithmetic<Rep1, Rep2> && equivalent_dim<D1, dim_invert<D2>>
[[nodiscard]] constexpr Scalar auto operator*(const quantity<D1, U1, Rep1>& lhs,
const quantity<D2, U2, Rep2>& rhs);
template<typename D1, typename U1, typename Rep1, typename D2, typename U2, typename Rep2>
requires detail::basic_arithmetic<Rep1, Rep2> && (!equivalent_dim<D1, dim_invert<D2>>)
[[nodiscard]] constexpr Quantity auto operator*(const quantity<D1, U1, Rep1>& lhs,
const quantity<D2, U2, Rep2>& rhs);
template<Scalar Value, typename D, typename U, typename Rep>
requires std::magma<std::ranges::divided_by, Value, Rep>
[[nodiscard]] constexpr Quantity auto operator/(const Value& v,
const quantity<D, U, Rep>& q);
template<typename D1, typename U1, typename Rep1, typename D2, typename U2, typename Rep2>
requires detail::basic_arithmetic<Rep1, Rep2> && equivalent_dim<D1, D2>
[[nodiscard]] constexpr Scalar auto operator/(const quantity<D1, U1, Rep1>& lhs,
const quantity<D2, U2, Rep2>& rhs);
template<typename D1, typename U1, typename Rep1, typename D2, typename U2, typename Rep2>
requires detail::basic_arithmetic<Rep1, Rep2> && (!equivalent_dim<D1, D2>)
[[nodiscard]] constexpr Quantity AUTO operator/(const quantity<D1, U1, Rep1>& lhs,
const quantity<D2, U2, Rep2>& rhs);
Additional functions provide the support for operations that result in a
different dimension type than those of their arguments. ``equivalent_dim``
constraint requires two dimensions to be either the same or have convertible
units of base dimension (with the same reference unit).
Beside adding new elements a few other changes where applied compared to the
`std::chrono::duration <https://en.cppreference.com/w/cpp/chrono/duration>`_
class template:
1. The ``duration`` is using ``std::common_type_t<Rep1, Rep2>`` to find a common
representation for a calculation result. Such a design was reported as problematic
by SG6 (numerics study group) members as sometimes we want to provide a different
type in case of multiplication and different in case of division. ``std::common_type``
lacks that additional information. That is why `units::quantity` uses the resulting
type of a concrete operator operation.
2. `operator %` is constrained with `treat_as_floating_point` type trait to limit the
types to integral representations only. Also `operator %(Rep)` takes `Rep` as a
template argument to limit implicit conversions.

BIN
docs/downcast_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
docs/downcast_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

8
docs/examples.rst Normal file
View File

@@ -0,0 +1,8 @@
Examples
========
.. toctree::
:maxdepth: 2
examples/hello_units
examples/avg_speed

View File

@@ -0,0 +1,8 @@
avg_speed
=========
.. literalinclude:: ../../example/avg_speed.cpp
:caption: avg_speed.cpp
:start-at: #include
:linenos:
:force:

View File

@@ -0,0 +1,7 @@
hello_units
===========
.. literalinclude:: ../../example/hello_units.cpp
:caption: hello_units.cpp
:start-at: #include
:linenos:

44
docs/faq.rst Normal file
View File

@@ -0,0 +1,44 @@
FAQ
===
General
-------
Why all UDLs are prefixed with ``q_`` instead of just using unit symbol?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Usage of only unit symbols in UDLs would be a preferred approach (less to type,
easier to understand and maintain). However, while increasing the coverage for
the library we learned that there are a lot unit symbols that conflict with
built-in types or numeric extensions. A few of those are: ``F`` (farad),
``J`` (joule), ``W`` (watt), ``K`` (kelvin), ``d`` (day),
``l`` or ``L`` (litre), ``erg``, ``ergps``. For a while for those we used ``_``
prefix to make the library work at all, but at some point we had to unify the
naming and we came up with ``q_`` prefix which results in a creation of
quantity of a provided unit.
Why dimensions depend on units and not vice versa?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Most of the libraries define units in terms of dimensions and this was also an
initial approach for this library. However it turns out that for such a design
it is hard to provide support for all the required scenarios.
The first of them is to support multiple unit systems (like SI, CGS, ...) where
each of can have a different base unit for the same dimension. Base quantity of
dimension length in SI has to know that it should use ``m`` to print the unit
symbol to the text output, while the same dimension for CGS should use ``cm``.
Also it helps in conversions among those systems.
The second one is to support natural units where more than one dimension can be
measured with the same unit (i.e. ``GeV``). Also if someone will decide to
implement a systems where SI quantities of the same kind are expressed as
different dimensions (i.e. height, width, and depth) all of them will just be
measured in meters.
Why do we spell ``metre`` instead of ``meter``?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Well, this is how ISO 80000 defines it (British English spelling by default).

18
docs/framework.rst Normal file
View File

@@ -0,0 +1,18 @@
Framework
=========
.. note::
For brevity all the code examples in this documentation will assume::
using namespace units;
.. toctree::
:maxdepth: 2
framework/basic_concepts
framework/quantities
framework/dimensions
framework/units
framework/conversions_and_casting
framework/text_output

View File

@@ -0,0 +1,22 @@
.. namespace:: units
Basic Concepts
==============
The most important concepts in the library are `Unit`, `Dimension`, and
`Quantity`:
.. image:: /_static/img/design.png
:align: center
`Unit` is a basic building block of the library. Every dimension works with
a concrete hierarchy of units. Such hierarchy defines a reference unit and
often a few scaled versions of it.
`Dimension` concept matches a dimension of either a base or derived quantity.
`base_dimension` is instantiated with a unique symbol identifier and a base
unit. `derived_dimension` is a list of exponents of either base or other
derived dimensions.
`Quantity` is a concrete amount of a unit for a specified dimension with a
specific representation.

View File

@@ -0,0 +1,16 @@
.. namespace:: units
Conversions and Casting
=======================
Implicit Conversions
--------------------
constructors
Explicit Casting
----------------
quantity_cast
Example of casting to a dimension's coherent unit.

View File

@@ -0,0 +1,178 @@
.. namespace:: units
Dimensions
==========
In the previous chapter we briefly introduced the notion of a physical
:term:`dimension`. Now it is time to learn much more about this subject.
Length, time, velocity, area, energy are only a few examples of physical
dimensions.
Operations
----------
Quantities of the same dimension can be easily added or subtracted with
each other and the result will always be a quantity of the same dimension:
.. code-block::
:emphasize-lines: 3-4
Length auto dist1 = 2q_m;
Length auto dist2 = 1q_m;
Length auto res1 = dist1 + dist2;
Length auto res2 = dist1 - dist2;
Additionally, we can always multiply or divide a quantity by a
:term:`scalar` and in such a case the quantity's dimension will also
not change:
.. code-block::
:emphasize-lines: 2-4
Length auto dist = 2q_m;
Length auto res1 = dist * 2; // 4 m
Length auto res2 = 3 * res1; // 12 m
Length auto res3 = res2 / 2; // 6 m
However, if we try to multiply or divide quantities of the same or
different dimensions, or we will divide a scalar by a quantity, we most
probably will always end up in a quantity of a yet another dimension:
.. code-block::
:emphasize-lines: 4-6
Length auto dist1 = 2q_m;
Length auto dist2 = 3q_m;
Time auto dur1 = 2q_s;
Area auto res1 = dist1 * dist2; // 6 m²
Velocity auto res2 = dist1 / dur1; // 1 m/s
Frequency auto res3 = 10 / dur1; // 5 Hz
However, please note that there is an exception from the above rule.
In case we divide the same dimensions, or multiply by the inverted
dimension, than we will end up with just a scalar type:
.. code-block::
:emphasize-lines: 4-5
Time auto dur1 = 10q_s;
Time auto dur2 = 2q_s;
Frequency auto fr1 = 5q_Hz;
Scalar auto v1 = dur1 / dur2; // 5
Scalar auto v2 = dur1 * fr1; // 50
Base dimensions
---------------
The quantities of base dimensions are called
:term:`base quantities <base quantity>` which are the atomic building blocks
of a :term:`system of quantities`. For example the The International System
of Units (:term:`SI`) defines 7 of them: length, mass, time, electric
current, thermodynamic temperature, substance, and luminous intensity.
To define a new base dimension the `base_dimension` class template is
provided. For example the SI base dimension of length can be defined as::
namespace si {
struct dim_length : base_dimension<"L", metre> {};
}
In the above code sample ``"L"`` is an base dimension's unique identifier
and `si::metre` is a :term:`base unit` of this base dimension. We can
obtain those back easily with::
static_assert(si::dim_length::symbol == "L");
static_assert(std::is_same_v<si::dim_length::base_unit, si::metre>);
Derived dimensions
------------------
The quantities of derived dimensions are called
:term:`derived quantities <derived quantity>` and are derived from base
quantities. This means that they are created by multiplying or dividing
quantities of other dimensions.
Looking at the previous code snippets the area, velocity, or frequency are
the examples of such quantities. Each derived quantity can be represented
as a unique list of exponents of base quantities. For example:
- an area is a length base quantity raised to the exponent ``2``
- a velocity is formed from the length base quantity with exponent ``1``
and time base quantity with exponent ``-1``.
The above dimensions can be defined in the library with the
`derived_dimension` class template as follows::
namespace si {
struct dim_area : derived_dimension<dim_area, square_metre,
exp<dim_length, 2>> {};
struct dim_velocity : derived_dimension<dim_velocity, metre_per_second,
exp<dim_length, 1>, exp<dim_time, -1>> {};
}
In the above code sample `si::square_metre` and `si::metre_per_second`
are the :term:`coherent derived units <coherent derived unit>` of those
derived dimensions.
Coherent unit argument is followed by the list of exponents that form this
derived dimension. This list is called a :term:`recipe` of this derived
dimension and may contain both base and derived dimensions. In the latter
case the dimension is being extracted to base dimensions by the framework
itself. The order and types of dimensions used in the recipe determine how
an dimension's unnamed unit symbol is being printed in the text output.
.. seealso::
More information on how the :term:`recipe` affect the printed symbol
of unnamed unit can be found in the :ref:`Derived Unnamed Units` chapter.
It is important to mention here that beside text output the order and
the number of elements in the `derived_dimension` definition does not
matter. Even if we define the above as:
.. code-block::
:emphasize-lines: 4, 6
namespace si {
struct dim_area : derived_dimension<dim_area, square_metre,
exp<dim_length, 1>, exp<dim_length, 1>> {};
struct dim_velocity : derived_dimension<dim_velocity, metre_per_second,
exp<dim_time, -1>, exp<dim_length, 1>> {};
}
the library will do its magic and will end up with the same
:term:`normalized derived dimension` which will allow the dimensional
analysis in the library to work as expected.
.. note::
The first template argument of `derived_dimension` is the type of the
child class inherited from the instantiation of this `derived_dimension`
class template. This is called a
:abbr:`CRTP (Curiously Recurring Template Parameter)` Idiom and is used
in many places in this library to provide :ref:`The Downcasting Facility`.
Hopefully if [P0847]_ will land in C++23 the additional CRTP-related
template parameter will be removed from this definition.
Obtaining a Unit of the Dimension
---------------------------------
In order to obtain the base/coherent unit of any dimension type a
`dimension_unit` helper was introduced::
static_assert(std::is_same_v<dimension_unit<si::dim_length>, si::metre>);
static_assert(std::is_same_v<dimension_unit<si::dim_velocity>, si::metre_per_second>);
.. rubric:: Citations:
.. [P0847] `"Deducing this" <https://wg21.link/P0847>`_, Programming Language C++ proposal

View File

@@ -0,0 +1,159 @@
.. namespace:: units
Quantities
==========
A :term:`quantity` is a concrete amount of a unit for a specified dimension
with a specific representation and is represented in the library with a
`quantity` class template.
Construction
------------
To create the quantity object from a :term:`scalar` we just have to pass
the value to the `quantity` class template explicit constructor::
quantity<si::dim_length, si::kilometre, double> d(123);
.. note::
As the constructor is explicit, the quantity object can be created from
an "unsafe" fundamental type only via
`direct initialization <https://en.cppreference.com/w/cpp/language/direct_initialization>`_.
This is why the code below using
`copy initialization <https://en.cppreference.com/w/cpp/language/copy_initialization>`_
**does not compile**::
quantity<si::dim_length, si::kilometre, double> d = 123; // ERROR
To simplify `quantity` objects creation the library provides helper aliases for
quantities of each :term:`dimension` which additionally set the representation
type to ``double`` by default::
namespace si {
template<Unit U, Scalar Rep = double>
using length = quantity<dim_length, U, Rep>;
}
Thanks to that, the above example can be rewritten as follows::
si::length<si::kilometre> d(123);
To further simplify construction of quantities with compile-time known
values the library provides :abbr:`UDL (User Defined Literal)` s for each
:term:`unit` of every :term:`dimension`. Thanks to them the same code can
be as simple as::
using namespace si::literals;
constexpr auto d1 = 123q_km; // si::length<si::kilometre, std::int64_t>
constexpr auto d2 = 123.q_km; // si::length<si::kilometre, long double>
``123q_km`` should be read as a quantity of length in kilometers. Initially the
library did not use the ``q_`` prefix for UDLs but it turned out that there are
a few unit symbols that collide with literals already existing in C and C++
language (i.e. ``F`` (farad), ``J`` (joule), ``W`` (watt), ``K`` (kelvin),
``d`` (day), ``l`` or ``L`` (litre), ``erg``, ``ergps``). This is why the
``q_`` prefix was consistently applied to all the UDLs.
Dimension-specific concepts
---------------------------
In case the user does not care about the specific unit and representation but
requires quantity of a concrete dimension than dimension-specific concepts can
be used::
using namespace si::literals;
constexpr Length auto d = 123q_km; // si::length<si::kilometre, std::int64_t>
.. note::
All instances of `quantity` class always match the `Quantity` concept.
All other regular types that are not quantities are called
:term:`scalars <scalar>` by the library and match the `Scalar` concept.
However, the above is not the most important usage of those concepts. Let's
assume that the user wants to implement an ``avg_speed`` function that will
be calculating the average speed based on provided distance and duration
quantities. The usage of such a function can look as follows::
using namespace si::literals;
using namespace international::literals;
constexpr Velocity auto v1 = avg_speed(220q_km, 2q_h);
constexpr Velocity auto v2 = avg_speed(140q_mi, 2q_h);
In this and all other physical units libraries such a function can be
implemented as::
constexpr si::velocity<si::metre_per_second> avg_speed(si::length<si::metre> d,
si::time<si::second> t)
{
return d / t;
}
While being correct, this function performs unnecessary intermediate
conversions (from kilometers to meters, from hours to seconds,
and from meters per second to kilometers per hour) which can affect
runtime performance and the precision of the final result. To eliminate
all that overhead we have to write a template function::
template<typename U1, typename R1, typename U2, typename R2>
constexpr auto avg_speed(si::length<U1, R1> d, si::time<U2, R2> t)
{
return d / t;
}
This function will work for every SI unit and representation without any
unnecessary overhead. It is also simple enough to prove its implementation
being correct just by a simple inspection. However, it might not always be
the case. For more complicated calculations we would like to ensure that we
are returning a physical quantity of a correct dimension. For this
dimension-specific concepts come handy again and with usage of C++20 generic
functions our function can look as simple as::
constexpr Velocity auto avg_speed(Length auto d, Time auto t)
{
return d / t;
}
Now we are sure that the dimension of returned quantity is correct. Also
please note that with the above code we implemented a truly generic function
that works efficiently not only with SI units but also with other systems of
units like CGS.
.. seealso::
Please refer to :ref:`avg_speed` example for more information on different
kinds of interfaces supported by the library.
Working with constrained deduced quantity types
-----------------------------------------------
It is important to note that when we assign a result from the function to an
automatically deduced type, even if it is constrained by a dimension-specific
concept, we still do not know what is the exact unit and representation type
of such a quantity. In many cases it might be exactly what we want to get,
but often we would like to know a specific type too. We have two options here:
- query the actual dimension, unit, and representation types::
constexpr Velocity auto v = avg_speed(220q_km, 2q_h);
using quantity_type = decltype(v);
using dimension_type = quantity_type::dimension;
using unit_type = quantity_type::unit;
using rep_type = quantity_type::rep;
- convert or cast to a desired quantity type::
constexpr Velocity auto v1 = avg_speed(220.q_km, 2q_h);
constexpr si::velocity<si::metre_per_second> v2 = v1;
constexpr Velocity auto v3 = quantity_cast<si::velocity<si::metre_per_second>(v1);
.. seealso::
More information on this subject can be found in :ref:`Conversions and Casting`
chapter.

View File

@@ -0,0 +1,45 @@
.. namespace:: units
Text output
===========
Beside providing dimensional analysis and units conversions, the library
also tries really hard to print any quantity in the most user friendly way.
Output streams
--------------
The easiest way to print a quantity is to provide its object to the output
stream::
using namespace si::literals;
using namespace international::literals;
constexpr Velocity auto v1 = avg_speed(220.q_km, 2q_h);
constexpr Velocity auto v2 = avg_speed(140.q_mi, 2q_h);
std::cout << v1 << '\n'; // 110 km/h
std::cout << v2 << '\n'; // 70 mi/h
The text output will always print the :term:`value of a quantity` followed
by the symbol of a :term:`unit` associated with this quantity. We will learn
more about units in the :ref:`Units` chapter, but for now, it is important
to remember that it is a good practice to always `quantity_cast()` a quantity
of an unknown ``auto`` type before passing it to the text output::
std::cout << quantity_cast<si::kilometre_per_hour>(v1) << '\n'; // 110 km/h
std::cout << quantity_cast<si::metre_per_second>(v1) << '\n'; // 30.5556 m/s
Formatting the output
---------------------
Grammar:
.. productionlist::
units-format-spec: fill-and-align[opt] sign[opt] width[opt] precision[opt] units-specs[opt]
units-specs: conversion-spec
: units-specs conversion-spec
: units-specs literal-char
literal-char: any character other than '{' or '}'
conversion-spec: '%' modifier[opt] type
modifier: one of 'E', 'O'
type: one of 'n', 'q', 'Q', 't', '%'

319
docs/framework/units.rst Normal file
View File

@@ -0,0 +1,319 @@
.. namespace:: units
Units
=====
Each quantity has a magnitude (a numerical value). In order to be able to
compare quantities of the same dimension a notion of a :term:`measurement unit`
was introduced. Units are designated by conventionally assigned names and
symbols. Thanks to them it is possible to compare two quantities of the
same dimension and express the ratio of the second quantity to the first
one as a number. For example ``10s`` is ``10`` times more than ``1s``.
Base quantities are expressed in terms of :term:`base units <base unit>`
(i.e. ``m`` (meter), ``s`` (second)), while derived quantities are expressed
in terms of :term:`derived units <derived unit>`.
Base Units
----------
:term:`Base units <base unit>` are the units of
:term:`base quantities <base quantity>` defined for
:term:`base dimensions <base dimension>`. For example in :term:`SI`
``m`` (meter) is a base unit of length, ``s`` (second) is a base unit of
time. In each :term:`coherent system of units`, there is only one base
unit for each base quantity. This is why a base unit type is required by
the `base_dimension` definition in this library. For example `si::dim_length`
can be defined in the following way::
namespace si {
struct dim_length : base_dimension<"L", metre> {};
}
where `si::metre` is defined as::
namespace si {
struct metre : named_unit<metre, "m", prefix> {};
}
In the above definition ``"m"`` is the unit symbol to be used in the text
output, and ``si::prefix`` specifies that the library should allow
definitions of prefixed units using `si::metre` as a reference (i.e.
`si::centimetre`).
.. seealso::
For more information on prefixes and scaling please refer to
`Scaled Units`_.
.. note::
The first template argument of `named_unit` is the type of the
child class inherited from the instantiation of this `named_unit`
class template. This is called a
:abbr:`CRTP (Curiously Recurring Template Parameter)` Idiom and is used
in many places in this library to provide :ref:`The Downcasting Facility`.
Hopefully if [P0847]_ will land in C++23 the additional CRTP-related
template parameter will be removed from this definition.
It is important to notice here that :term:`SI` is not the only system used
in the industry and the library has to support other systems too. Also
in some cases conversions between such systems should be allowed. The
fact that the `base_dimension` takes the base unit in its definition makes
it really easy to define other systems of units. For example length in the
`CGS <https://en.wikipedia.org/wiki/Centimetre%E2%80%93gram%E2%80%93second_system_of_units>`_
could be defined as::
namespace cgs {
struct dim_length : base_dimension<"L", si::centimetre> {};
}
The fact that both base dimensions use the same identifier ``"L"`` tells
the library that bot definitions refer to the same physical dimension of
length. The only difference is the measurement unit used to define their
base dimensions. Thanks to using `si::centimetre` in the `cgs::dim_length`
definition we also enabled the ability to easily convert between those
2 base dimensions (as the library knows how to convert `si::metre` to
`si::centimetre` and vice versa).
Derived Units
-------------
Derived units can be either named or unnamed.
Derived Named Units
^^^^^^^^^^^^^^^^^^^
Derived named units have a unique symbol (i.e. ``N`` (newton) or ``Pa``
(pascal)) and they are defined in the same way as base units (which
always have to be a named unit)::
namespace si {
struct newton : named_unit<newton, "N", prefix> {};
}
Derived Unnamed Units
^^^^^^^^^^^^^^^^^^^^^
Derived unnamed units are the units where the symbol is derived from the
base quantities symbols and the expression of the dependence of the derived
quantity on the base quantities (i.e. ``m/s`` (metre per second), ````
(square metre)). To support such use cases a library introduced a notion of
:term:`derived dimension recipe` which stores the information about the
order, exponents, and types of dimensions used to defined this particular
derived dimension. For example each of the below ``momentum`` definitions
will result in a different unnamed unit symbol:
.. code-block::
:emphasize-lines: 2-4, 6-8, 10-12
struct dim_momentum : derived_dimension<dim_momentum, kilogram_metre_per_second,
exp<si::dim_mass, 1>,
exp<si::dim_length, 1>,
exp<si::dim_time, -1>> {}; // kg⋅m/s
struct dim_momentum : derived_dimension<dim_momentum, kilogram_metre_per_second,
exp<si::dim_length, 1>,
exp<si::dim_mass, 1>,
exp<si::dim_time, -1>> {}; // m⋅kg/s
struct dim_momentum : derived_dimension<dim_momentum, kilogram_metre_per_second,
exp<si::dim_time, -1>,
exp<si::dim_length, 1>,
exp<si::dim_mass, 1>> {}; // 1/s⋅m⋅kg
where ``kilogram_metre_per_second`` is defined as::
struct kilogram_metre_per_second : unit<kilogram_metre_per_second> {};
However, the easiest way to define momentum is just to use the
`si::velocity` derived dimension in the recipe:
.. code-block::
:emphasize-lines: 3
struct dim_momentum : derived_dimension<dim_momentum, kilogram_metre_per_second,
exp<si::dim_mass, 1>,
exp<si::dim_velocity, 1>> {}; // kg⋅m/s
In such a case the library will do its magic and will automatically
unpack a provided derived dimension to its base dimensions in order to
end up with a :term:`normalized derived dimension` for a parent entity.
The need to support a derived dimension in the recipe is not just a
syntactic sugar that allows us to do less typing. It is worth to notice
here that some of the derived unnamed units are defined in terms of other
derived named units (i.e. surface tension quantity is measured in terms
of ``N/m``):
.. code-block::
:emphasize-lines: 2
struct dim_surface_tension : derived_dimension<dim_surface_tension, newton_per_metre,
exp<si::dim_force, 1>,
exp<si::dim_length, -1>> {}; // N/m
If we defined the above in terms of base units we would end up with
a ``kg/s²`` derived unit symbol.
Scaled Units
------------
Until now we talked mostly about
:term:`coherent units <coherent derived unit>` which are units used to
define dimensions and thus, in their system of units, have proportionality
factor/ratio equals one. However quantities of each dimension can also use
other units of measurement to describe their magnitude (numerical value).
Scaled Units
^^^^^^^^^^^^
We are used to use minutes, hours, or days to measure quantities of time.
Those units are the scaled versions of a time dimension's base unit,
namely second. Those can be defined easily in the library using
`named_scaled_unit` class template::
struct minute : named_scaled_unit<minute, "min", no_prefix, ratio<60>, second> {};
struct hour : named_scaled_unit<hour, "h", no_prefix, ratio<60>, minute> {};
struct day : named_scaled_unit<hour, "d", no_prefix, ratio<24>, hour> {};
where `no_prefix` is a special tag type describing that the library should
not allow to define a new prefixed unit that would use this unit as a
reference ("kilohours" does not have much sense, right?). The `ratio` type
used in the definition is really similar to ``std::ratio`` but it takes
the third additional argument that defines the exponent of the ratio.
Thanks to it we can address nearly infinite scaling factors between units
and define units like:
.. code-block::
:force:
struct electronvolt : named_scaled_unit<electronvolt, "eV", prefix,
ratio<1'602'176'634, 1'000'000'000, -19>, joule> {};
..
TODO Submit a bug for above lexing problem
Finally, the last of the `named_scaled_unit` class template parameters
provide a reference unit for scaling. Please note that it can be a dimension's
base/coherent unit (like `si::second`) or any other unit (i.e. `si::minute`,
`si::hour`) that is a scaled version of the dimension's base/coherent unit.
Prefixed Unit
^^^^^^^^^^^^^
Prefixed units are just scaled units with a standardized ratio. For example
:term:`SI` defines prefixes based on the exponent of ``10``. Here is a
complete list of all the :term:`SI` prefixes supported by the library::
namespace si {
struct prefix : prefix_type {};
struct yocto : units::prefix<yocto, prefix, "y", ratio<1, 1, -24>> {};
struct zepto : units::prefix<zepto, prefix, "z", ratio<1, 1, -21>> {};
struct atto : units::prefix<atto, prefix, "a", ratio<1, 1, -18>> {};
struct femto : units::prefix<femto, prefix, "f", ratio<1, 1, -15>> {};
struct pico : units::prefix<pico, prefix, "p", ratio<1, 1, -12>> {};
struct nano : units::prefix<nano, prefix, "n", ratio<1, 1, -9>> {};
struct micro : units::prefix<micro, prefix, "µ", ratio<1, 1, -6>> {};
struct milli : units::prefix<milli, prefix, "m", ratio<1, 1, -3>> {};
struct centi : units::prefix<centi, prefix, "c", ratio<1, 1, -2>> {};
struct deci : units::prefix<deci, prefix, "d", ratio<1, 1, -1>> {};
struct deca : units::prefix<deca, prefix, "da", ratio<1, 1, 1>> {};
struct hecto : units::prefix<hecto, prefix, "h", ratio<1, 1, 2>> {};
struct kilo : units::prefix<kilo, prefix, "k", ratio<1, 1, 3>> {};
struct mega : units::prefix<mega, prefix, "M", ratio<1, 1, 6>> {};
struct giga : units::prefix<giga, prefix, "G", ratio<1, 1, 9>> {};
struct tera : units::prefix<tera, prefix, "T", ratio<1, 1, 12>> {};
struct peta : units::prefix<peta, prefix, "P", ratio<1, 1, 15>> {};
struct exa : units::prefix<exa, prefix, "E", ratio<1, 1, 18>> {};
struct zetta : units::prefix<zetta, prefix, "Z", ratio<1, 1, 21>> {};
struct yotta : units::prefix<yotta, prefix, "Y", ratio<1, 1, 24>> {};
}
Alternative hierarchy of prefixes is the one used in data information
domain:
.. code-block::
:force:
namespace data {
struct prefix : prefix_type {};
struct kibi : units::prefix<kibi, prefix, "Ki", ratio< 1'024>> {};
struct mebi : units::prefix<mebi, prefix, "Mi", ratio< 1'048'576>> {};
struct gibi : units::prefix<gibi, prefix, "Gi", ratio< 1'073'741'824>> {};
struct tebi : units::prefix<tebi, prefix, "Ti", ratio< 1'099'511'627'776>> {};
struct pebi : units::prefix<pebi, prefix, "Pi", ratio< 1'125'899'906'842'624>> {};
struct exbi : units::prefix<exbi, prefix, "Ei", ratio<1'152'921'504'606'846'976>> {};
}
With the definitions like above we can easily define prefixed unit. For
example we can define `si::kilometre` as::
namespace si {
struct kilometre : prefixed_unit<kilometre, kilo, metre> {};
}
.. important::
Prefixed units have to use named units as a reference. For unnamed
units we could end up with some strange, misleading, and sometimes
wrong definitions ("kilo square metre" seams strange and spelled
as ``km²`` would be invalid).
Deduced Units
^^^^^^^^^^^^^
For some units determining of a correct scaling ratio may not be trivial,
and even if done correctly, may be a pain to maintain. For a simple example
let's take a "kilometre per hour" unit. What is the easiest to maintain
ratio in reference to "metre per second":
- ``1000/3600``
- ``10/36``
- ``5/18``
Whichever, we choose there will always be someone not happy with our choice.
Thanks to a `deduced_unit` class template provided by the library this problem
does not exist at all. With it `si::kilometre_per_hour` can be defined as::
namespace si {
struct kilometre_per_hour : deduced_unit<kilometre_per_hour, dim_velocity, kilometre, hour> {};
}
Please note that this is the only unit-related class template that takes
a dimension as its parameter. This derived dimension provides a :term:`recipe`
used for its definition. Based on the information stored in the recipe
(order, type, and exponents of composite dimensions) and the ratios of units
provided in the template parameter list after the derived dimension parameter,
the library calculates the final ratio for this unit.
.. rubric:: Citations:
.. [P0847] `"Deducing this" <https://wg21.link/P0847>`_, Programming Language C++ proposal

4
docs/genindex.rst Normal file
View File

@@ -0,0 +1,4 @@
.. This file is a placeholder and will be replaced during Sphinx build
Index
#####

214
docs/glossary.rst Normal file
View File

@@ -0,0 +1,214 @@
.. default-role:: term
Glossary
========
ISO 80000 [1]_ definitions
--------------------------
.. glossary::
quantity
- Property of a phenomenon, body, or substance, where the property has a magnitude that can
be expressed by means of a number and a reference.
- A reference can be a measurement unit, a measurement procedure, a reference material, or
a combination of such.
- A quantity as defined here is a scalar. However, a vector or a tensor, the components of
which are quantities, is also considered to be a quantity.
- The concept quantity may be generically divided into, e.g. physical quantity,
chemical quantity, and biological quantity, or base quantity and derived quantity.
- Examples of quantities are: mass, length, density, magnetic field strength, etc.
kind of quantity
kind
- Aspect common to mutually comparable `quantities <quantity>`.
- The division of the concept quantity into several kinds is to some extent arbitrary
- i.e. the quantities diameter, circumference, and wavelength are generally considered
to be quantities of the same kind, namely, of the kind of quantity called length.
- Quantities of the same kind within a given `system of quantities` have the same quantity
`dimension`. However, `quantities <quantity>` of the same `dimension` are not necessarily
of the same kind.
- For example, the absorbed dose and the dose equivalent have the same `dimension`.
However, the former measures the absolute amount of radiation one receives whereas
the latter is a weighted measurement taking into account the kind of radiation
on was exposed to.
system of quantities
system
- Set of `quantities <quantity>` together with a set of non-contradictory equations
relating those `quantities <quantity>`.
- Examples of systems of quantities are: the International System of Quantities,
the Imperial System, etc.
base quantity
- `Quantity` in a conventionally chosen subset of a given `system of quantities`, where
no `quantity` in the subset can be expressed in terms of the other `quantities <quantity>`
within that subset.
- Base quantities are referred to as being mutually independent since a base quantity
cannot be expressed as a product of powers of the other base quantities.
derived quantity
- `Quantity`, in a `system of quantities`, defined in terms of the base quantities of
that system.
International System of Quantities
ISQ
- `System of quantities` based on the seven `base quantities <base quantity>`:
length, mass, time, electric current, thermodynamic temperature, amount of substance,
and luminous intensity.
- The International System of Units (SI) is based on the ISQ.
dimension of a quantity
quantity dimension
dimension
- Expression of the dependence of a `quantity` on the `base quantities <base quantity>`
of a `system of quantities` as a product of powers of factors corresponding to the
`base quantities <base quantity>`, omitting any numerical factors.
- A power of a factor is the factor raised to an exponent. Each factor is the dimension
of a `base quantity`.
- In deriving the dimension of a quantity, no account is taken of its scalar, vector, or
tensor character.
- In a given `system of quantities`:
- `quantities <quantity>` of the same `kind` have the same quantity dimension,
- `quantities <quantity>` of different quantity dimensions are always of different `kinds <kind>`,
- `quantities <quantity>` having the same quantity dimension are not necessarily of the same `kind`.
quantity of dimension one
dimensionless quantity
- `quantity` for which all the exponents of the factors corresponding to the
`base quantities <base quantity>` in its `quantity dimension` are zero.
- The term “dimensionless quantity” is commonly used and is kept here for historical
reasons. It stems from the fact that all exponents are zero in the symbolic
representation of the `dimension` for such `quantities <quantity>`. The term “quantity
of dimension one” reflects the convention in which the symbolic representation of the
`dimension` for such `quantities <quantity>` is the symbol ``1``. This `dimension` is
not a number, but the neutral element for multiplication of `dimensions <dimension>`.
- The `measurement units <measurement unit>` and values of quantities of dimension one
are numbers, but such `quantities <quantity>` convey more information than a number.
- Some quantities of dimension one are defined as the ratios of two
`quantities of the same kind <kind>`. The `coherent derived unit` is the number one,
symbol ``1``.
- Numbers of entities are quantities of dimension one.
unit of measurement
measurement unit
unit
- Real scalar `quantity`, defined and adopted by convention, with which any other
`quantity of the same kind <kind>` can be compared to express the ratio of the
second `quantity` to the first one as a number.
- Measurement units are designated by conventionally assigned names and symbols.
- Measurement units of `quantities <quantity>` of the same `quantity dimension` may
be designated by the same name and symbol even when the `quantities <quantity>` are
not of the same `kind`.
For example, joule per kelvin and J/K are respectively the name and symbol of both a
measurement unit of heat capacity and a measurement unit of entropy, which are generally
not considered to be `quantities of the same kind <kind>`. However, in some cases special
measurement unit names are restricted to be used with `quantities <quantity>` of specific
`kind` only. For example, the measurement unit second to the power minus one (``1/s``) is
called hertz (``Hz``) when used for frequencies and becquerel (``Bq``) when used for
activities of radionuclides. As another example, the joule (``J``) is used as a unit of
energy, but never as a unit of moment of force, i.e. the newton metre (``N · m``).
- Measurement units of `quantities of dimension one <quantity of dimension one>` are
numbers. In some cases, these measurement units are given special names, e.g. radian,
steradian, and decibel, or are expressed by quotients such as millimole per mole equal
to :math:`10^{3}` and microgram per kilogram equal to :math:`10^{9}`.
base unit
- Measurement unit that is adopted by convention for a `base quantity`.
- In each `coherent system of units`, there is only one base unit for each `base quantity`.
- A base unit may also serve for a `derived quantity` of the same `quantity dimension`.
- For example, the `ISQ` has the base units of: metre, kilogram, second, Ampere, Kelvin, mole,
and candela.
derived unit
- Measurement unit for a `derived quantity`.
- For example, in the `ISQ` Newton, Pascal, and katal are derived units.
coherent derived unit
- `Derived unit` that, for a given `system of quantities` and for a chosen set of
`base units <base unit>`, is a product of powers of `base units <base unit>` with no
other proportionality factor than one.
- A power of a `base unit` is the `base unit` raised to an exponent.
- Coherence can be determined only with respect to a particular `system of quantities`
and a given set of `base units <base unit>`. That is, if the metre and the second are
base units, the metre per second is the coherent derived unit of velocity.
system of units
- Set of `base units <base unit>` and `derived units <derived unit>`, together with
their multiples and submultiples, defined in accordance with given rules, for a given
`system of quantities`.
coherent system of units
- `System of units`, based on a given `system of quantities`, in which the measurement
unit for each `derived quantity` is a `coherent derived unit`.
- A `system of units` can be coherent only with respect to a `system of quantities` and
the adopted `base units <base unit>`.
off-system measurement unit
off-system unit
- `Measurement unit` that does not belong to a given `system of units`. For example, the
electronvolt (:math:`≈ 1,602 18 × 10^{19} J`) is an off-system measurement unit of energy with
respect to the `SI` or day, hour, minute are off-system measurement units of time with
respect to the `SI`.
International System of Units
SI
- `System of units`, based on the `International System of Quantities`, their names and
symbols, including a series of prefixes and their names and symbols, together with rules
for their use, adopted by the General Conference on Weights and Measures (CGPM)
quantity value
value of a quantity
value
- Number and reference together expressing magnitude of a `quantity`.
- A quantity value can be presented in more than one way.
Other definitions
-----------------
.. glossary::
:sorted:
base dimension
- A `dimension` of a `base quantity`.
derived dimension
- A `dimension` of a `derived quantity`.
- Often implemented as a list of exponents of `base dimensions <base dimension>`.
normalized derived dimension
A `derived dimension` in which:
- `base dimensions <base dimension>` are not repeated in a list (each base dimension is provided at most once),
- `base dimensions <base dimension>` are consistently ordered,
- `base dimensions <base dimension>` having zero exponent are elided.
derived dimension recipe
recipe
- The ordered list of exponents used to define a derived dimension
- The list may contain both base and derived dimensions (in the latter case
the dimension is being extracted to base dimensions by the framework)
- The order and types of dimensions used in the recipe determine how an unnamed
dimension's unit symbol is being printed in the text output
scalar
- Not a `quantity`
- Can be passed as a representation type to the :class:`units::quantity` type or be used as a factor
while multiplying or dividing a `quantity`.
.. rubric:: Footnotes:
.. [1] **ISO 80000-1:2009(E) "Quantities and units — Part 1: General"** gives general information
and definitions concerning quantities, systems of quantities, units, quantity and unit symbols,
and coherent unit systems, especially the International System of Quantities, ISQ, and the
International System of Units, SI. The principles laid down in ISO 80000-1:2009 are intended
for general use within the various fields of science and technology and as an introduction to
other parts of the Quantities and units series. Ordinal quantities and nominal properties are
outside the scope of ISO 80000-1:2009.

49
docs/index.rst Normal file
View File

@@ -0,0 +1,49 @@
Welcome to mp-units!
====================
**mp-units** is a compile-time enabled Modern C++ library that provides compile-time dimensional
analysis and unit/quantity manipulation. Source code is hosted on `GitHub <https://github.com/mpusz/units>`_
with a permissive `MIT license <https://github.com/mpusz/units/blob/master/LICENSE.md>`_.
.. important::
The **mp-units** library is the subject of this ISO C++ paper: `P1935 <https://wg21.link/p1935>`_.
It is explained in this `CppCon 2019 talk <https://youtu.be/0YW6yxkdhlU>`_ (slightly dated now).
We are working towards potentially having it standardized for C++23 and are actively looking
for parties interested in field trialing the library.
.. note::
As this library targets C++23 and extensively uses C++20 features as of now it compiles correctly
only with gcc-9.1 and newer.
.. toctree::
:maxdepth: 2
:caption: Getting Started:
introduction
quick_start
usage
framework
scenarios
design
examples
faq
.. toctree::
:maxdepth: 1
:caption: Reference:
reference/concepts
reference/types
reference/functions
reference/systems
.. toctree::
:maxdepth: 1
:caption: Appendix:
glossary
genindex
CHANGELOG

47
docs/introduction.rst Normal file
View File

@@ -0,0 +1,47 @@
Introduction
============
**mp-units** is a compile-time enabled Modern C++ library that provides compile-time
dimensional analysis and unit/quantity manipulation. The basic idea and design
heavily bases on `std::chrono::duration <https://en.cppreference.com/w/cpp/chrono/duration>`_
and extends it to work properly with many dimensions.
Thanks to compile-time operations no runtime execution cost is introduced,
facilitating the use of this library to provide dimension checking in
performance-critical code. Support for quantities and units for arbitrary unit
system models and arbitrary value types is provided, as is a fine-grained
general facility for unit conversions.
The library architecture has been designed with flexibility and extensibility
in mind. The demonstrations of the ease of adding new dimensions, their units,
and unit conversions are provided in the :ref:`Examples`.
Open Source
-----------
**mp-units** is Free and Open Source, with a permissive
`MIT license <https://github.com/mpusz/units/blob/master/LICENSE.md>`_. Check
out the source code and issue tracking (for questions and support, reporting
bugs and suggesting feature requests and improvements) at https://github.com/mpusz/units.
Approach
--------
1. Safety and performance
- strong types
- compile-time safety
- ``constexpr`` all the things
- as fast or even faster than when working with fundamental types
2. The best possible user experience
- compiler errors
- debugging
3. No macros in the user interface
4. Easy extensibility
5. No external dependencies
6. Possibility to be standardized as a freestanding part of the C++ Standard
Library

46
docs/nomnoml.md Normal file
View File

@@ -0,0 +1,46 @@
# nomnoml
Graphs in the documentation are created with <http://www.nomnoml.com>.
## Concepts
```text
[<abstract>Dimension|
[base_dimension<Symbol, Unit>]<-[exp<Dimension, Num, Den>]
[derived_dimension<Child, Unit, Exponent...>]<-[exp<Dimension, Num, Den>]
[exp<Dimension, Num, Den>]<-[derived_dimension<Child, Unit, Exponent...>]
]
[<abstract>Quantity|
[quantity<Dimension, Unit, Rep>]
]
[<abstract>Unit]<-[Dimension]
[Dimension]<-[Quantity]
[Unit]<-[Quantity]
```
## Units
```text
#direction: right
[scaled_unit<Ratio, Unit>]<:-[unit<Child>]
[scaled_unit<Ratio, Unit>]<:-[named_unit<Child, Symbol, PrefixType>]
[scaled_unit<Ratio, Unit>]<:-[named_scaled_unit<Child, Symbol, PrefixType, Ratio, Unit>]
[scaled_unit<Ratio, Unit>]<:-[prefixed_unit<Child, Prefix, Unit>]
[scaled_unit<Ratio, Unit>]<:-[deduced_unit<Child, Dimension, Unit, Unit...>]
```
## Downcasting 1
```text
[detail::derived_dimension_base<exp<si::dim_length, 2>>]<:-[dim_area]
```
## Downcasting 2
```text
[downcast_base<detail::derived_dimension_base<exp<si::dim_length, 2>>>]<:-[detail::derived_dimension_base<exp<si::dim_length, 2>>]
[detail::derived_dimension_base<exp<si::dim_length, 2>>]<:-[downcast_child<dim_area, detail::derived_dimension_base<exp<si::dim_length, 2>>>]
[downcast_child<dim_area, detail::derived_dimension_base<exp<si::dim_length, 2>>>]<:-[dim_area]```

62
docs/quick_start.rst Normal file
View File

@@ -0,0 +1,62 @@
Quick Start
===========
Here is a small example of possible operations::
// simple numeric operations
static_assert(10q_km / 2 == 5q_km);
// unit conversions
static_assert(1q_h == 3600q_s);
static_assert(1q_km + 1q_m == 1001q_m);
// dimension conversions
static_assert(2q_m * 3q_m == 6q_m2);
static_assert(10q_km / 5q_km == 2);
static_assert(1000 / 1q_s == 1q_kHz);
static_assert(1q_km / 1q_s == 1000q_mps);
static_assert(2q_kmph * 2q_h == 4q_km);
static_assert(2q_km / 2q_kmph == 1q_h);
.. admonition:: Try it on Compiler Explorer
`Example #1 <https://godbolt.org/z/BZjWbD>`_
This library requires some C++20 features (concepts, classes as
:abbr:`NTTP (Non-Type Template Parameter)`, ...). Thanks to them the user gets a powerful
but still easy to use interface where all unit conversions and dimensional analysis can be
performed without sacrificing on accuracy. Please see the below example for a quick preview
of basic library features::
#include <units/physical/si/velocity.h>
#include <units/physical/international/velocity.h>
#include <iostream>
using namespace units;
constexpr Velocity auto avg_speed(Length auto d, Time auto t)
{
return d / t;
}
int main()
{
using namespace si::literals;
Velocity auto v1 = avg_speed(220q_km, 2q_h);
Velocity auto v2 = avg_speed(si::length<international::mile>(140), si::time<si::hour>(2));
Velocity auto v3 = quantity_cast<si::metre_per_second>(v2);
Velocity auto v4 = quantity_cast<int>(v3);
std::cout << v1 << '\n'; // 110 km/h
std::cout << v2 << '\n'; // 70 mi/h
std::cout << v3 << '\n'; // 31.2928 m/s
std::cout << v4 << '\n'; // 31 m/s
}
.. admonition:: Try it on Compiler Explorer
`Example #2 <https://godbolt.org/z/_Yx6D7>`_
.. seealso::
You can find more code examples in the :ref:`Examples` chapter.

View File

@@ -0,0 +1,62 @@
Concepts
========
.. namespace:: units
.. note::
All names defined in this chapter reside in the :any:`units` namespace unless specified otherwise.
.. concept:: template<typename T> PrefixType
Satisfied by all types derived from :class:`prefix_type`.
.. concept:: template<typename T> Prefix
Satisfied by all instantiations of :class:`prefix`.
.. concept:: template<typename T> Ratio
Satisfied by all instantiations of :class:`ratio`.
.. concept:: template<typename R> UnitRatio
Satisfied by all types that satisfy :expr:`Ratio<R>` and for which :expr:`R::num > 0` and :expr:`R::den > 0`.
.. concept:: template<typename T> BaseDimension
Satisfied by all dimension types derived from instantiation of :class:`base_dimension`.
.. concept:: template<typename T> Exponent
Satisfied by all instantiations of :class:`exp`.
.. concept:: template<typename T> DerivedDimension
Satisfied by all dimension types derived from instantiation of :class:`detail::derived_dimension_base`.
.. concept:: template<typename T> Dimension
Satisfied by all dimension types for which either :expr:`BaseDimension<T>` or :expr:`DerivedDimension<T>` is ``true``.
.. concept:: template<typename T> Unit
Satisfied by all unit types derived from instantiation of :class:`scaled_unit`.
.. concept:: template<typename U, typename D> UnitOf
Satisfied by all unit types that satisfy :expr:`Unit<U>`, :expr:`Dimension<D>`, and for which
:expr:`U::reference` and :expr:`dimension_unit<D>::reference` denote the same unit type.
.. concept:: template<typename T> Quantity
Satisfied by all instantiations of :class:`quantity`.
.. concept:: template<typename T> WrappedQuantity
Satisfied by all wrapper types that satisfy :expr:`Quantity<typename T::value_type>` recursively
(i.e. :expr:`std::optional<si::length<si::metre>>`).
.. concept:: template<typename T> Scalar
Satisfied by types that satisfy :expr:`(!Quantity<T>) && (!WrappedQuantity<T>) && std::regular<T>`.

View File

@@ -0,0 +1,15 @@
.. note::
All names defined in this chapter reside in the :any:`units` namespace unless specified otherwise.
Functions
=========
.. doxygenfunction:: quantity_cast
Metafunctions
=============
.. doxygentypedef:: dimension_unit

View File

@@ -0,0 +1,32 @@
.. note::
All names defined in this chapter reside in the :any:`units` namespace unless specified otherwise.
Systems
=======
SI
--
..
doxygennamespace:: units::si
:members:
:undoc-members:
:outline:
File
----
..
doxygenfile:: si/length.h
Group
-----
..
doxygengroup:: si_length
:content-only:
:members:
:undoc-members:
:outline:

51
docs/reference/types.rst Normal file
View File

@@ -0,0 +1,51 @@
.. note::
All names defined in this chapter reside in the :any:`units` namespace unless specified otherwise.
Types
=====
Quantity
--------
.. doxygenclass:: units::quantity
:members:
..
:undoc-members:
Dimension
---------
.. doxygenstruct:: units::base_dimension
:members:
.. doxygenstruct:: units::derived_dimension
:members:
Unit
----
.. doxygenstruct:: units::scaled_unit
:members:
.. doxygenstruct:: units::unit
:members:
.. doxygenstruct:: units::named_unit
:members:
.. doxygenstruct:: units::named_scaled_unit
:members:
.. doxygenstruct:: units::prefixed_unit
:members:
.. doxygenstruct:: units::deduced_unit
:members:
Ratio
-----
.. doxygenstruct:: units::ratio
:members:

3
docs/requirements.txt Normal file
View File

@@ -0,0 +1,3 @@
sphinx
recommonmark
breathe

15
docs/scenarios.rst Normal file
View File

@@ -0,0 +1,15 @@
Scenarios
=========
.. note::
For brevity all the code examples in this documentation will assume::
using namespace units;
.. toctree::
:maxdepth: 2
scenarios/unknown_units_and_dimensions
scenarios/legacy_interfaces
scenarios/extensions

View File

@@ -0,0 +1,19 @@
.. namespace:: units
Extending the library
=====================
Custom Units
------------
Custom Dimensions
-----------------
Custom Base Dimensions
^^^^^^^^^^^^^^^^^^^^^^
Custom Derived Dimensions
^^^^^^^^^^^^^^^^^^^^^^^^^
Custom Systems
--------------

View File

@@ -0,0 +1,41 @@
.. namespace:: units
Working with Legacy Interfaces
==============================
In case we are working with a legacy/unsafe interface we may be forced to
extract a :term:`value of a quantity` with :func:`quantity::count()` and
pass it to the library's output:
.. code-block::
:caption: legacy.h
namespace legacy {
void print_eta(double speed_in_mps);
} // namespace legacy
.. code-block::
#include "legacy.h"
#include <units/physical/si/velocity.h>
using namespace units;
constexpr Velocity auto avg_speed(Length auto d, Time auto t)
{
return d / t;
}
void print_eta(Length auto d, Time auto t)
{
Velocity auto v = avg_speed(d, t);
legacy::print_eta(quantity_cast<si::metre_per_second>(v).count());
}
.. important::
When dealing with a quantity of an unknown ``auto`` type please remember
to always use `quantity_cast` to cast it to a desired unit before calling
`quantity::count()` and passing the raw value to the legacy/unsafe interface.

View File

@@ -0,0 +1,9 @@
.. namespace:: units
Working with Unknown Units and Dimensions
=========================================
- what is an unknown unit?
- what is an unknown dimension?
- temporary result
- casting to the coherent unit

BIN
docs/units.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

233
docs/usage.rst Normal file
View File

@@ -0,0 +1,233 @@
Usage
=====
.. note::
As this library targets C++23 and extensively uses C++20 features as of now it compiles correctly
only with gcc-9.1 and newer.
Repository structure and dependencies
-------------------------------------
This repository contains three independent CMake-based projects:
- *./src*
- header-only project containing whole **mp-units** library
- when C++20 support will be fully supported by C++ compilers this library will have
no external dependencies but until then it depends on
`range-v3 <https://github.com/ericniebler/range-v3>`_ (only for gcc versions < 10.0)
and `{fmt} <https://github.com/fmtlib/fmt>`_ libraries.
- *.*
- project used as an entry point for library development and CI/CD
- it wraps *./src* project together with usage examples and tests
- additionally to the dependencies of *./src* project, it uses:
- `Catch2 <https://github.com/catchorg/Catch2>`_ library as a unit tests framework.
- `linear algebra <https://github.com/BobSteagall/wg21/tree/master/linear_algebra/code>`_
library based on proposal `P1385 <https://wg21.link/P1385>`_ used in some examples
and tests.
- `Doxygen <http://www.doxygen.nl>`_ to extract C++ entities information from the source
code.
- `Sphinx <https://www.sphinx-doc.org>`_ to build the documentation.
- `Sphinx recommonmark <https://recommonmark.readthedocs.io>`_.
- `Breathe <https://breathe.readthedocs.io/>`_ as a bridge between the Sphinx and Doxygen
documentation systems.
- *./test_package*
- library installation and Conan package verification
.. note::
Please note that **mp-units** repository also depends on a git submodule in the
*./cmake/common* subdirectory providing some common CMake utilities.
Obtaining Dependencies
----------------------
This library assumes that most of the dependencies will be provided by the
`Conan Package Manager <https://conan.io/>`_. In case you would like to obtain needed
dependencies by other means some modifications to library's CMake files will be needed.
The rest of the dependencies are provided by :command:`python3-pip`.
Conan quick intro
^^^^^^^^^^^^^^^^^
In case you are not familiar with Conan, to install it (or upgrade) just do:
.. code-block:: shell
pip3 install -U conan
After that you might need to add a custom profile file for your development environment
in *~/.conan/profile* directory. An example profile can look as follows:
.. code-block:: ini
:emphasize-lines: 8
[settings]
os=Linux
os_build=Linux
arch=x86_64
arch_build=x86_64
compiler=gcc
compiler.version=9
compiler.cppstd=20
compiler.libcxx=libstdc++11
build_type=Release
[options]
[build_requires]
[env]
CC=/usr/bin/gcc-9
CXX=/usr/bin/g++-9
.. tip::
Please note that **mp-units** library requires C++20 to be set either in a Conan profile or forced
via Conan command line. If you do the former, you will not need to provide ``-s compiler.cppstd=20``
every time your rune a Conan command line (as it is suggested below).
Non-standard Conan remotes
^^^^^^^^^^^^^^^^^^^^^^^^^^
Add the following remotes to your local Conan instance:
.. code-block:: shell
conan remote add conan-mpusz https://api.bintray.com/conan/mpusz/conan-mpusz
conan remote add bincrafters https://api.bintray.com/conan/bincrafters/public-conan
conan remote add linear-algebra https://api.bintray.com/conan/twonington/public-conan
.. note::
The last two remotes are needed only if you plan to build all of the code and documentation
in **mp-units** repository.
Build options
-------------
Environment variables
^^^^^^^^^^^^^^^^^^^^^
.. envvar:: CONAN_RUN_TESTS
**Defaulted to**: Not defined (``True``/``False`` if defined)
Enables compilation of all the source code (tests and examples) and building the documentation.
To support that it requires some additional Conan build dependencies described in
`Repository structure and dependencies`_.
It also runs unit tests during Conan build.
Building, Installation, and Reuse
---------------------------------
There are a few different ways of installing/reusing **mp-units** in your project.
Copy
^^^^
As **mp-units** is a C++ header-only library you can simply copy *src/include* directory to
your source tree and use it as regular header files.
CMake + Conan
^^^^^^^^^^^^^
To use **mp-units** as a CMake imported library the following steps may be performed:
1. Clone the repository together with its submodules:
.. code-block:: shell
git clone --recurse-submodules https://github.com/mpusz/units.git
or in case it is already cloned without submodules, initialize, fetch, and checkout them with:
.. code-block:: shell
git submodule update --init
2. Create Conan configuration file (either *conanfile.txt* or *conanfile.py*) in your
project's top-level directory and add **mp-units** as a dependency to your Conan configuration
file.
- for example to use **mp-units** testing/prerelease version ``0.5.0`` in case of *conanfile.txt*
it is enough for it to just contain the following lines:
.. code-block:: ini
[requires]
mp-units/0.5.0@mpusz/testing
3. Import Conan dependencies definitions to the beginning of your top-level *CMakeLists.txt*
file in your project:
.. code-block:: cmake
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup(TARGETS)
4. Link your CMake target with **mp-units**:
.. code-block:: cmake
target_link_libraries(<your_target> PUBLIC|PRIVATE|INTERFACE CONAN_PKG::mp-units)
5. Download and install Conan dependencies before running CMake configuration step:
.. code-block:: shell
cd build
conan install .. -pr <your_conan_profile> -s compiler.cppstd=20 -b=outdated -u
6. Configure your CMake project as usual.
Full **mp-units** build, unit testing, and documentation generation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In case you would like to build all the source code (with unit tests and examples) and documentation
in **mp-units** repository, you should use the *CMakeLists.txt* from the top-level directory,
obtain Python dependencies, and run Conan with :envvar:`CONAN_RUN_TESTS` = ``True``:
.. code-block:: shell
git clone --recurse-submodules https://github.com/mpusz/units.git && cd units
pip3 install -r docs/requirements.txt
mkdir build && cd build
conan install .. -pr <your_conan_profile> -s compiler.cppstd=20 -e CONAN_RUN_TESTS=True -b outdated
cmake .. -DCMAKE_BUILD_TYPE=Release
cmake --build .
ctest
The above will download and install all of the dependencies needed for the development of the library,
build all of the source code and documentation, and run unit tests.
Packaging
---------
To test CMake installation and Conan packaging or create a Conan package run:
.. code-block:: shell
git clone --recurse-submodules https://github.com/mpusz/units.git && cd units
pip3 install -r docs/requirements.txt
conan create . <username>/<channel> -pr <your_conan_profile> -s compiler.cppstd=20 -e CONAN_RUN_TESTS=True -b outdated
The above will create a Conan package and run tests provided in *./test_package* directory.
Uploading **mp-units** package to the Conan server
--------------------------------------------------
.. code-block:: shell
conan upload -r <remote-name> --all mp-units/0.5.0@<user>/<channel>