mirror of
https://github.com/mpusz/mp-units.git
synced 2025-06-24 16:51:33 +02:00
units building and packaging redesigned + Travis CI support
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@ -29,8 +29,9 @@
|
||||
*.app
|
||||
|
||||
# IDE-specific
|
||||
.*/
|
||||
cmake-build-*/
|
||||
.idea/
|
||||
.vscode/
|
||||
*build*/
|
||||
|
||||
# Conan
|
||||
*.pyc
|
||||
|
34
.travis.yml
Normal file
34
.travis.yml
Normal file
@ -0,0 +1,34 @@
|
||||
env:
|
||||
global:
|
||||
- CONAN_USERNAME: "mpusz"
|
||||
- CONAN_LOGIN_USERNAME: "mpusz"
|
||||
- CONAN_CHANNEL: "testing"
|
||||
- CONAN_UPLOAD: "https://api.bintray.com/conan/mpusz/conan-mpusz "
|
||||
- CONAN_STABLE_BRANCH_PATTERN: "release/*"
|
||||
- CONAN_UPLOAD_ONLY_WHEN_STABLE: 0
|
||||
- CONAN_DOCKER_32_IMAGES: 1
|
||||
|
||||
linux: &linux
|
||||
os: linux
|
||||
dist: xenial
|
||||
language: python
|
||||
python: "3.7"
|
||||
services:
|
||||
- docker
|
||||
osx: &osx
|
||||
os: osx
|
||||
language: generic
|
||||
matrix:
|
||||
include:
|
||||
- <<: *linux
|
||||
env: CONAN_GCC_VERSIONS=7 CONAN_DOCKER_IMAGE=conanio/gcc7
|
||||
- <<: *linux
|
||||
env: CONAN_GCC_VERSIONS=8 CONAN_DOCKER_IMAGE=conanio/gcc8
|
||||
|
||||
install:
|
||||
- chmod +x .travis/install.sh
|
||||
- ./.travis/install.sh
|
||||
|
||||
script:
|
||||
- chmod +x .travis/run.sh
|
||||
- ./.travis/run.sh
|
24
.travis/install.sh
Normal file
24
.travis/install.sh
Normal file
@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
set -x
|
||||
|
||||
if [[ "$(uname -s)" == 'Darwin' ]]; then
|
||||
brew update || brew update
|
||||
brew outdated pyenv || brew upgrade pyenv
|
||||
brew install pyenv-virtualenv
|
||||
brew install cmake || true
|
||||
|
||||
if which pyenv > /dev/null; then
|
||||
eval "$(pyenv init -)"
|
||||
fi
|
||||
|
||||
pyenv install 3.7.1
|
||||
pyenv virtualenv 3.7.1 conan
|
||||
pyenv rehash
|
||||
pyenv activate conan
|
||||
fi
|
||||
|
||||
pip install conan_package_tools
|
||||
pip install conan --upgrade
|
||||
conan user
|
13
.travis/run.sh
Normal file
13
.travis/run.sh
Normal file
@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
set -x
|
||||
|
||||
if [[ "$(uname -s)" == 'Darwin' ]]; then
|
||||
if which pyenv > /dev/null; then
|
||||
eval "$(pyenv init -)"
|
||||
fi
|
||||
pyenv activate conan
|
||||
fi
|
||||
|
||||
python build.py
|
@ -39,7 +39,6 @@ include(compile_flags)
|
||||
add_subdirectory(src)
|
||||
|
||||
# add unit tests
|
||||
enable_testing()
|
||||
add_subdirectory(test)
|
||||
|
||||
# add usage example
|
||||
|
@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 Mateusz Pusz
|
||||
Copyright (c) 2016 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
|
456
README.md
456
README.md
@ -1,10 +1,15 @@
|
||||
[](https://raw.githubusercontent.com/mpusz/units/master/LICENSE)
|
||||
[](https://travis-ci.org/mpusz/units)
|
||||
[](https://ci.appveyor.com/project/mpusz/units)
|
||||
[](https://bintray.com/mpusz/conan-mpusz/units%3Ampusz/_latestVersion)
|
||||
|
||||
# `units` - Physical Units Library for C++
|
||||
|
||||
## Summary
|
||||
|
||||
`Units` is a compile-time enabled Modern C++ library that provides support for converting physical
|
||||
units and dimensions. The basic idea and design heavily bases on `std::chrono::duration` and extends
|
||||
it to work properly with many dimensions.
|
||||
`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:
|
||||
|
||||
@ -19,444 +24,25 @@ static_assert(2_kmph * 2_h == 4_km);
|
||||
static_assert(2_km / 2_kmph == 1_h);
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
1. Safety and performance
|
||||
- strong types
|
||||
- template metaprogramming
|
||||
- `constexpr` all the things
|
||||
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 part of the C++ Standard Library
|
||||
## Repository structure
|
||||
|
||||
That repository contains the following `cmake`-based projects:
|
||||
- `./src` - header-only project for `units`
|
||||
- `.` - project used for development needs that wraps `./src` project together with
|
||||
usage examples and unit tests
|
||||
- `./test_package` - library installation and conan package verification
|
||||
|
||||
## Basic Concepts
|
||||
Please note that all projects depend on some `cmake` modules in `./cmake` directory.
|
||||
|
||||
### `Dimensions`
|
||||
|
||||
`units::dimension` is a type-list like type that stores an ordered list of exponents of one
|
||||
or more base dimensions:
|
||||
## Building, testing and installation
|
||||
|
||||
```cpp
|
||||
template<Exponent... Es>
|
||||
struct dimension : upcast_base<dimension<Es...>> {};
|
||||
```
|
||||
For detailed information on project compilation, testing and reuse please refer to
|
||||
[doc/INSTALL.md](doc/INSTALL.md).
|
||||
|
||||
`units::Dimension` is a Concept that is satisfied by a type that is empty and publicly
|
||||
derived from `units::dimension` class template:
|
||||
|
||||
```cpp
|
||||
template<typename T>
|
||||
concept Dimension =
|
||||
std::is_empty_v<T> &&
|
||||
detail::is_dimension<typename T::base_type> && // exposition only
|
||||
DerivedFrom<T, typename T::base_type>;
|
||||
```
|
||||
## Library design
|
||||
|
||||
#### `Exponents`
|
||||
|
||||
`units::exp` provides an information about a single base dimension and its exponent in a derived
|
||||
dimension:
|
||||
|
||||
```cpp
|
||||
template<typename BaseDimension, int Value>
|
||||
struct exp {
|
||||
using dimension = BaseDimension;
|
||||
static constexpr int value = Value;
|
||||
};
|
||||
```
|
||||
|
||||
where `BaseDimension` is a unique sortable compile-time value and for now is implemented as:
|
||||
|
||||
```cpp
|
||||
template<int UniqueValue>
|
||||
using dim_id = std::integral_constant<int, UniqueValue>;
|
||||
```
|
||||
|
||||
but it is meant to be replaced with C++20 class `constexpr` values provided as non-type template
|
||||
parameters (when feature will be available in a compiler) so that for example base dimension for
|
||||
length will be expressed as `dimension<exp<"length", 1>>`.
|
||||
|
||||
`units::Exponent` concept is satisfied if provided type is an instantiation of `units::exp` class
|
||||
template:
|
||||
|
||||
```cpp
|
||||
template<typename T>
|
||||
concept Exponent =
|
||||
detail::is_exp<T>; // exposition only
|
||||
```
|
||||
|
||||
#### `make_dimension`
|
||||
|
||||
Above design of dimensions is created with the ease of use for end users in mind. Compile-time
|
||||
errors should provide as short as possible template instantiations strings that should be easy to
|
||||
understand by every engineer. Also types visible in a debugger should be easy to understand.
|
||||
That is why `units::dimension` type for derived dimensions always stores information about only
|
||||
those base dimensions that are used to form that derived dimension.
|
||||
|
||||
However, such an approach have some challenges:
|
||||
|
||||
```cpp
|
||||
constexpr Velocity auto v1 = 1_m / 1_s;
|
||||
constexpr Velocity auto v2 = 2 / 2_s * 1_m;
|
||||
|
||||
static_assert(Same<decltype(v1), decltype(v2)>);
|
||||
static_assert(v1 == v2);
|
||||
```
|
||||
|
||||
Above code, no matter what is the order of the base dimensions in an expression forming our result,
|
||||
must produce the same `Velocity` type so that both values can be easily compared. In order to achieve
|
||||
that, `dimension` class templates should never be instantiated manually but through a `make_dimension_t`
|
||||
template metaprogramming factory function:
|
||||
|
||||
```cpp
|
||||
template<Exponent... Es>
|
||||
struct make_dimension {
|
||||
using type = /* unspecified */;
|
||||
};
|
||||
|
||||
template<Exponent... Es>
|
||||
using make_dimension_t = typename make_dimension<Es...>::type;
|
||||
```
|
||||
|
||||
So for example to create a `dimension_velocity` type we have to do:
|
||||
|
||||
```cpp
|
||||
struct dimension_velocity : make_dimension_t<exp<base_dim_length, 1>, exp<base_dim_time, -1>> {};
|
||||
```
|
||||
|
||||
In order to make `make_dimension_t` work as expected it has to provide unique ordering for
|
||||
contained base dimensions. Beside providing ordering to base dimensions it also has to:
|
||||
- aggregate two arguments of the same base dimension but different exponents
|
||||
- eliminate two arguments of the same base dimension and with opposite equal exponents
|
||||
|
||||
Additionally, it would be good if the final type produced by `make_dimension_t` would be easy to
|
||||
understand for the user. For example we may decide to order base dimensions with decreasing order of
|
||||
their exponents. That is why second sorting of a type list may be required. For example:
|
||||
|
||||
```cpp
|
||||
template<Exponent... Es>
|
||||
struct make_dimension {
|
||||
using type = mp::type_list_sort_t<detail::dim_consolidate_t<mp::type_list_sort_t<dimension<Es...>, exp_dim_id_less>>, exp_greater_equal>;
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
#### `merge_dimension`
|
||||
|
||||
`units::merge_dimension` is similar to `make_dimension` but instead of sorting the whole list
|
||||
of base dimensions from scratch it assumes that provided input `dimension` types are already
|
||||
sorted as a result of `make_dimension`.
|
||||
|
||||
Typical use case for `merge_dimension` is to produce final `dimension` return type of multiplying
|
||||
two different dimensions:
|
||||
|
||||
```cpp
|
||||
template<Dimension D1, Dimension D2>
|
||||
struct dimension_multiply;
|
||||
|
||||
template<Exponent... E1, Exponent... E2>
|
||||
struct dimension_multiply<dimension<E1...>, dimension<E2...>> {
|
||||
using type = upcasting_traits_t<merge_dimension_t<dimension<E1...>, dimension<E2...>>>;
|
||||
};
|
||||
|
||||
template<Dimension D1, Dimension D2>
|
||||
using dimension_multiply_t = typename dimension_multiply<typename D1::base_type, typename D2::base_type>::type;
|
||||
```
|
||||
|
||||
Example implementation of `merge_dimension` may look like:
|
||||
|
||||
```cpp
|
||||
template<Dimension D1, Dimension D2>
|
||||
struct merge_dimension {
|
||||
using type = mp::type_list_sort_t<detail::dim_consolidate_t<mp::type_list_merge_sorted_t<D1, D2, exp_dim_id_less>>, exp_greater_equal>;
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
### `Units`
|
||||
|
||||
`units::unit` is a class template that expresses the unit of a specific physical dimension:
|
||||
|
||||
```cpp
|
||||
template<Dimension D, Ratio R>
|
||||
requires (R::num > 0)
|
||||
struct unit : upcast_base<unit<D, R>> {
|
||||
using dimension = D;
|
||||
using ratio = R;
|
||||
};
|
||||
```
|
||||
|
||||
`units::Unit` is a Concept that is satisfied by a type that is empty and publicly
|
||||
derived from `units::unit` class template:
|
||||
|
||||
```cpp
|
||||
template<typename T>
|
||||
concept Unit =
|
||||
std::is_empty_v<T> &&
|
||||
detail::is_unit<typename T::base_type> && // exposition only
|
||||
DerivedFrom<T, typename T::base_type>;
|
||||
```
|
||||
|
||||
### `Quantities`
|
||||
|
||||
`units::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, Unit U, Number Rep>
|
||||
requires Same<D, typename U::dimension>
|
||||
class quantity;
|
||||
```
|
||||
|
||||
`units::Quantity` is a Concept that is satisfied by a type that is an instantiation of `units::quantity`
|
||||
class template:
|
||||
|
||||
```cpp
|
||||
template<typename T>
|
||||
concept Quantity =
|
||||
detail::is_quantity<T>; // exposition only
|
||||
```
|
||||
|
||||
`units::quantity` provides the interface really similar to `std::chrono::duration` with additional
|
||||
member types and functions as below:
|
||||
|
||||
```cpp
|
||||
template<Dimension D, Unit U, Number Rep>
|
||||
requires Same<D, typename U::dimension>
|
||||
class quantity {
|
||||
public:
|
||||
using dimension = D;
|
||||
using unit = U;
|
||||
|
||||
template<Dimension D1, Unit U1, Number Rep1, Dimension D2, Unit U2, Number Rep2>
|
||||
requires treat_as_floating_point<std::common_type_t<Rep1, Rep2>> || std::ratio_multiply<typename U1::ratio, typename U2::ratio>::den == 1
|
||||
quantity<dimension_multiply_t<D1, D2>, upcasting_traits_t<unit<dimension_multiply_t<D1, D2>, std::ratio_multiply<typename U1::ratio, typename U2::ratio>>>, std::common_type_t<Rep1, Rep2>>
|
||||
constexpr operator*(const quantity<D1, U1, Rep1>& lhs,
|
||||
const quantity<D2, U2, Rep2>& rhs);
|
||||
|
||||
template<Number Rep1, Dimension D, Unit U, Number Rep2>
|
||||
quantity<dim_invert_t<D>, upcasting_traits_t<unit<dim_invert_t<D>, std::ratio<U::ratio::den, U::ratio::num>>>, std::common_type_t<Rep1, Rep2>>
|
||||
constexpr operator/(const Rep1& v,
|
||||
const quantity<D, U, Rep2>& q);
|
||||
|
||||
template<Dimension D1, Unit U1, Number Rep1, Dimension D2, Unit U2, Number Rep2>
|
||||
requires treat_as_floating_point<std::common_type_t<Rep1, Rep2>> || std::ratio_divide<typename U1::ratio, typename U2::ratio>::den == 1
|
||||
quantity<dimension_divide_t<D1, D2>, upcasting_traits_t<unit<dimension_divide_t<D1, D2>, std::ratio_divide<typename U1::ratio, typename U2::ratio>>>, std::common_type_t<Rep1, Rep2>>
|
||||
constexpr 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.
|
||||
|
||||
#### `quantity_cast`
|
||||
|
||||
To explicitly force truncating conversions `quantity_cast` function is provided which is a direct
|
||||
counterpart of `std::chrono::duration_cast`.
|
||||
|
||||
## Strong types instead of aliases, and type upcasting capability
|
||||
|
||||
Most of the important design decisions in the library are dictated by the requirement of providing
|
||||
the best user experience as possible.
|
||||
|
||||
For example with template aliases usage the following code:
|
||||
|
||||
```cpp
|
||||
const Velocity t = 20_s;
|
||||
```
|
||||
|
||||
could generate a following compile time error:
|
||||
|
||||
```text
|
||||
C:\repos\units\example\example.cpp:39:22: error: deduced initializer does not satisfy placeholder constraints
|
||||
const Velocity t = 20_s;
|
||||
^~~~
|
||||
In file included from C:\repos\units\example\example.cpp:23:
|
||||
C:/repos/units/src/include/units/si/velocity.h:41:16: note: within 'template<class T> concept const bool units::Velocity<T> [with T = units::quantity<units::dimension<units::exp<units::base_dim_time, 1> >, units::unit<units::dimension<units::exp<units::base_dim_time, 1> >, std::ratio<1> >, long long int>]'
|
||||
concept Velocity = Quantity<T> && Same<typename T::dimension, dimension_velocity>;
|
||||
^~~~~~~~
|
||||
In file included from C:/repos/units/src/include/units/bits/tools.h:25,
|
||||
from C:/repos/units/src/include/units/dimension.h:25,
|
||||
from C:/repos/units/src/include/units/si/base_dimensions.h:25,
|
||||
from C:/repos/units/src/include/units/si/velocity.h:25,
|
||||
from C:\repos\units\example\example.cpp:23:
|
||||
C:/repos/units/src/include/units/bits/stdconcepts.h:33:18: note: within 'template<class T, class U> concept const bool mp::std_concepts::Same<T, U> [with T = units::dimension<units::exp<units::base_dim_time, 1> >; U = units::dimension<units::exp<units::base_dim_length, 1>, units::exp<units::base_dim_time, -1> >]'
|
||||
concept Same = std::is_same_v<T, U>;
|
||||
^~~~
|
||||
C:/repos/units/src/include/units/bits/stdconcepts.h:33:18: note: 'std::is_same_v' evaluated to false
|
||||
```
|
||||
|
||||
Time and velocity are not that complicated dimensions and there are much more complicated dimensions
|
||||
out there, but even for those dimensions
|
||||
|
||||
```text
|
||||
[with T = units::quantity<units::dimension<units::exp<units::base_dim_time, 1> >, units::unit<units::dimension<units::exp<units::base_dim_time, 1> >, std::ratio<1> >, long long int>]
|
||||
```
|
||||
|
||||
and
|
||||
|
||||
```text
|
||||
[with T = units::dimension<units::exp<units::base_dim_time, 1> >; U = units::dimension<units::exp<units::base_dim_length, 1>, units::exp<units::base_dim_time, -1> >]
|
||||
```
|
||||
|
||||
starts to be really hard to analyze or debug.
|
||||
|
||||
That is why it was decided to provide automated upcasting capability when possible. With that the
|
||||
same code will result with such an error:
|
||||
|
||||
```text
|
||||
C:\repos\units\example\example.cpp:40:22: error: deduced initializer does not satisfy placeholder constraints
|
||||
const Velocity t = 20_s;
|
||||
^~~~
|
||||
In file included from C:\repos\units\example\example.cpp:23:
|
||||
C:/repos/units/src/include/units/si/velocity.h:48:16: note: within 'template<class T> concept const bool units::Velocity<T> [with T = units::quantity<units::dimension_time, units::second, long long int>]'
|
||||
concept Velocity = Quantity<T> && Same<typename T::dimension, dimension_velocity>;
|
||||
^~~~~~~~
|
||||
In file included from C:/repos/units/src/include/units/bits/tools.h:25,
|
||||
from C:/repos/units/src/include/units/dimension.h:25,
|
||||
from C:/repos/units/src/include/units/si/base_dimensions.h:25,
|
||||
from C:/repos/units/src/include/units/si/velocity.h:25,
|
||||
from C:\repos\units\example\example.cpp:23:
|
||||
C:/repos/units/src/include/units/bits/stdconcepts.h:33:18: note: within 'template<class T, class U> concept const bool mp::std_concepts::Same<T, U> [with T = units::dimension_time; U = units::dimension_velocity]'
|
||||
concept Same = std::is_same_v<T, U>;
|
||||
^~~~
|
||||
C:/repos/units/src/include/units/bits/stdconcepts.h:33:18: note: 'std::is_same_v' evaluated to false
|
||||
```
|
||||
|
||||
Now
|
||||
|
||||
```text
|
||||
[with T = units::quantity<units::dimension_time, units::second, long long int>]
|
||||
```
|
||||
|
||||
and
|
||||
|
||||
```text
|
||||
[with T = units::dimension_time; U = units::dimension_velocity]
|
||||
```
|
||||
|
||||
are not arguably much easier to understand thus provide better user experience.
|
||||
|
||||
Upcasting capability is provided through dedicated `upcasting_traits` and by `base_type` member
|
||||
type in `upcast_base` class template.
|
||||
|
||||
```cpp
|
||||
template<typename T>
|
||||
struct upcasting_traits : std::type_identity<T> {};
|
||||
|
||||
template<typename T>
|
||||
using upcasting_traits_t = typename upcasting_traits<T>::type;
|
||||
```
|
||||
|
||||
```cpp
|
||||
struct dimension_length : make_dimension_t<exp<base_dim_length, 1>> {};
|
||||
|
||||
template<>
|
||||
struct upcasting_traits<typename dimension_length::base_type> :
|
||||
std::type_identity<dimension_length> {};
|
||||
```
|
||||
|
||||
```cpp
|
||||
struct kilometer : unit<dimension_length, std::kilo> {};
|
||||
|
||||
template<>
|
||||
struct upcasting_traits<typename kilometer::base_type> :
|
||||
std::type_identity<kilometer> {};
|
||||
```
|
||||
|
||||
|
||||
## Adding new dimensions
|
||||
|
||||
In order to extend the library with custom dimensions the user has to:
|
||||
1. Create a new dimension type and provide upcasting trait for it:
|
||||
|
||||
```cpp
|
||||
struct dimension_velocity : make_dimension_t<exp<base_dim_length, 1>, exp<base_dim_time, -1>> {};
|
||||
template<> struct upcasting_traits<typename dimension_velocity::base_type> : std::type_identity<dimension_velocity> {};
|
||||
```
|
||||
|
||||
2. Define the base unit (`std::ratio<1>`) and secondary ones and provide upcasting traits for them via:
|
||||
|
||||
```cpp
|
||||
struct meter_per_second : unit<dimension_velocity, std::ratio<1>> {};
|
||||
template<> struct upcasting_traits<typename meter_per_second::base_type> : std::type_identity<meter_per_second> {};
|
||||
```
|
||||
|
||||
3. Define a concept that will match a new dimension:
|
||||
|
||||
```cpp
|
||||
template<typename T>
|
||||
concept Velocity = Quantity<T> && Same<typename T::dimension, dimension_velocity>;
|
||||
```
|
||||
|
||||
4. Provide user-defined literals for the most important units:
|
||||
|
||||
```cpp
|
||||
namespace literals {
|
||||
constexpr auto operator""_mps(unsigned long long l) { return velocity<meter_per_second, std::int64_t>(l); }
|
||||
constexpr auto operator""_mps(long double l) { return velocity<meter_per_second, long double>(l); }
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Adding new base dimensions
|
||||
|
||||
For now base dimensions are defined in terms of `std::integral_constant<int, ...>` and the provided
|
||||
values must be unique. For example:
|
||||
|
||||
```cpp
|
||||
struct base_dim_length : dim_id<0> {};
|
||||
struct base_dim_mass : dim_id<1> {};
|
||||
struct base_dim_time : dim_id<2> {};
|
||||
struct base_dim_electric_current : dim_id<3> {};
|
||||
struct base_dim_temperature : dim_id<4> {};
|
||||
struct base_dim_amount_of_substance : dim_id<5> {};
|
||||
struct base_dim_luminous_intensity : dim_id<6> {};
|
||||
```
|
||||
|
||||
However, as soon as C++20 class type values will be supported as non-type template parameters
|
||||
base dimensions will be just a text values. For example:
|
||||
|
||||
```cpp
|
||||
inline constexpr base_dim base_dim_length = "length";
|
||||
```
|
||||
|
||||
With that it should be really easy to add support for any new non-standard base units to the
|
||||
library without the risk of collision with any dimension type defined by the library itself or
|
||||
by other users extending the library with their own dimension types.
|
||||
|
||||
Additionally, it should make the error logs even shorter thus easier to understand.
|
||||
|
||||
|
||||
## Open questions
|
||||
|
||||
1. Should we ensure that dimension is always a result of make_dimension? How to do it?
|
||||
|
||||
2. Should we provide strong types and upcasting_traits for `quantity` type?
|
||||
|
||||
In such a case all the operators have to be provided to a child class. Or maybe use CRTP?
|
||||
|
||||
3. What to do with `time` which is ambiguous?
|
||||
|
||||
4. What to do with `std::chrono::duration`?
|
||||
|
||||
5. What is the best way to add support for temperatures?
|
||||
|
||||
Temperatures require not only require `std::ratio` but also should adjusted/shifted by some
|
||||
constant values (i.e. [°C] = [K] − 273.15).
|
||||
|
||||
6. Should the "base dimension" be better expressed/isolated by the design?
|
||||
|
||||
7. `seconds<int>` or `time<second, int>`?
|
||||
|
||||
8. How to use CTAD?
|
||||
|
||||
CTAD for alias templates were already supported by EWG in San Diego 2018 so `length(3.5)`
|
||||
will work. However,deduction with partial argument lists was rejected so `length<mile>(3)`
|
||||
will not be supported for now.
|
||||
`units` library design rationale and documentation can be found in
|
||||
[doc/DESIGN.md](doc/DESIGN.md)
|
||||
|
6
build.py
Normal file
6
build.py
Normal file
@ -0,0 +1,6 @@
|
||||
from cpt.packager import ConanMultiPackager
|
||||
|
||||
if __name__ == "__main__":
|
||||
builder = ConanMultiPackager()
|
||||
builder.add_common_builds(shared_option_name=False, pure_c=False)
|
||||
builder.run()
|
25
conanfile.py
25
conanfile.py
@ -26,21 +26,38 @@ class UnitsConan(ConanFile):
|
||||
name = "units"
|
||||
version = "0.0.1"
|
||||
author = "Mateusz Pusz"
|
||||
license = "https://github.com/mpusz/units/blob/master/LICENSE"
|
||||
license = "https://github.com/mpusz/units/blob/master/LICENSE.md"
|
||||
url = "https://github.com/mpusz/units"
|
||||
description = "Physical Units library for C++"
|
||||
exports = ["LICENSE.md"]
|
||||
settings = "os", "compiler", "build_type", "arch"
|
||||
requires = (
|
||||
"cmcstl2/2019.03.18@mpusz/stable",
|
||||
"gsl-lite/0.33.0@nonstd-lite/stable"
|
||||
)
|
||||
scm = {
|
||||
"type": "git",
|
||||
"url": "auto",
|
||||
"revision": "auto"
|
||||
}
|
||||
generators = "cmake"
|
||||
|
||||
def build(self):
|
||||
def _configure_cmake(self):
|
||||
cmake = CMake(self)
|
||||
cmake.configure(source_dir="%s/src" % self.source_folder)
|
||||
return cmake
|
||||
|
||||
def build(self):
|
||||
cmake = self._configure_cmake()
|
||||
cmake.build()
|
||||
# cmake.install()
|
||||
|
||||
def package(self):
|
||||
self.copy(pattern="*license*", dst="licenses", excludes="cmake/common/*", ignore_case=True, keep_path=False)
|
||||
cmake = self._configure_cmake()
|
||||
cmake.install()
|
||||
|
||||
def package_info(self):
|
||||
self.cpp_info.libs = ["units"]
|
||||
self.cpp_info.includedirs = ['include']
|
||||
|
||||
def package_id(self):
|
||||
self.info.header_only()
|
||||
|
498
doc/DESIGN.md
Normal file
498
doc/DESIGN.md
Normal file
@ -0,0 +1,498 @@
|
||||
# `units` - Physical Units Library for C++
|
||||
|
||||
## Summary
|
||||
|
||||
`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
|
||||
static_assert(1000 / 1_s == 1_kHz);
|
||||
static_assert(1_h == 3600_s);
|
||||
static_assert(1_km + 1_m == 1001_m);
|
||||
static_assert(10_km / 5_km == 2);
|
||||
static_assert(10_km / 2 == 5_km);
|
||||
static_assert(1_km / 1_s == 1000_mps);
|
||||
static_assert(2_kmph * 2_h == 4_km);
|
||||
static_assert(2_km / 2_kmph == 1_h);
|
||||
```
|
||||
|
||||
|
||||
## Requirements
|
||||
|
||||
1. Safety and performance
|
||||
- strong types
|
||||
- template metaprogramming
|
||||
- `constexpr` all the things
|
||||
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 part of the C++ Standard Library
|
||||
|
||||
|
||||
## Basic Concepts
|
||||
|
||||
### `Dimensions`
|
||||
|
||||
`units::dimension` is a type-list like type that stores an ordered list of exponents of one
|
||||
or more base dimensions:
|
||||
|
||||
```cpp
|
||||
template<Exponent... Es>
|
||||
struct dimension : upcast_base<dimension<Es...>> {};
|
||||
```
|
||||
|
||||
`units::Dimension` is a Concept that is satisfied by a type that is empty and publicly
|
||||
derived from `units::dimension` class template:
|
||||
|
||||
```cpp
|
||||
template<typename T>
|
||||
concept Dimension =
|
||||
std::is_empty_v<T> &&
|
||||
detail::is_dimension<typename T::base_type> && // exposition only
|
||||
DerivedFrom<T, typename T::base_type>;
|
||||
```
|
||||
|
||||
#### `Exponents`
|
||||
|
||||
`units::exp` provides an information about a single base dimension and its exponent in a derived
|
||||
dimension:
|
||||
|
||||
```cpp
|
||||
template<typename BaseDimension, int Value>
|
||||
struct exp {
|
||||
using dimension = BaseDimension;
|
||||
static constexpr int value = Value;
|
||||
};
|
||||
```
|
||||
|
||||
where `BaseDimension` is a unique sortable compile-time value and for now is implemented as:
|
||||
|
||||
```cpp
|
||||
template<int UniqueValue>
|
||||
using dim_id = std::integral_constant<int, UniqueValue>;
|
||||
```
|
||||
|
||||
but it is meant to be replaced with C++20 class `constexpr` values provided as non-type template
|
||||
parameters (when feature will be available in a compiler) so that for example base dimension for
|
||||
length will be expressed as `dimension<exp<"length", 1>>`.
|
||||
|
||||
`units::Exponent` concept is satisfied if provided type is an instantiation of `units::exp` class
|
||||
template:
|
||||
|
||||
```cpp
|
||||
template<typename T>
|
||||
concept Exponent =
|
||||
detail::is_exp<T>; // exposition only
|
||||
```
|
||||
|
||||
#### `make_dimension`
|
||||
|
||||
Above design of dimensions is created with the ease of use for end users in mind. Compile-time
|
||||
errors should provide as short as possible template instantiations strings that should be easy to
|
||||
understand by every engineer. Also types visible in a debugger should be easy to understand.
|
||||
That is why `units::dimension` type for derived dimensions always stores information about only
|
||||
those base dimensions that are used to form that derived dimension.
|
||||
|
||||
However, such an approach have some challenges:
|
||||
|
||||
```cpp
|
||||
constexpr Velocity auto v1 = 1_m / 1_s;
|
||||
constexpr Velocity auto v2 = 2 / 2_s * 1_m;
|
||||
|
||||
static_assert(Same<decltype(v1), decltype(v2)>);
|
||||
static_assert(v1 == v2);
|
||||
```
|
||||
|
||||
Above code, no matter what is the order of the base dimensions in an expression forming our result,
|
||||
must produce the same `Velocity` type so that both values can be easily compared. In order to achieve
|
||||
that, `dimension` class templates should never be instantiated manually but through a `make_dimension_t`
|
||||
template metaprogramming factory function:
|
||||
|
||||
```cpp
|
||||
template<Exponent... Es>
|
||||
struct make_dimension {
|
||||
using type = /* unspecified */;
|
||||
};
|
||||
|
||||
template<Exponent... Es>
|
||||
using make_dimension_t = typename make_dimension<Es...>::type;
|
||||
```
|
||||
|
||||
So for example to create a `dimension_velocity` type we have to do:
|
||||
|
||||
```cpp
|
||||
struct dimension_velocity : make_dimension_t<exp<base_dim_length, 1>, exp<base_dim_time, -1>> {};
|
||||
```
|
||||
|
||||
In order to make `make_dimension_t` work as expected it has to provide unique ordering for
|
||||
contained base dimensions. Beside providing ordering to base dimensions it also has to:
|
||||
- aggregate two arguments of the same base dimension but different exponents
|
||||
- eliminate two arguments of the same base dimension and with opposite equal exponents
|
||||
|
||||
Additionally, it would be good if the final type produced by `make_dimension_t` would be easy to
|
||||
understand for the user. For example we may decide to order base dimensions with decreasing order of
|
||||
their exponents. That is why second sorting of a type list may be required. For example:
|
||||
|
||||
```cpp
|
||||
template<Exponent... Es>
|
||||
struct make_dimension {
|
||||
using type = mp::type_list_sort_t<detail::dim_consolidate_t<mp::type_list_sort_t<dimension<Es...>, exp_dim_id_less>>, exp_greater_equal>;
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
#### `merge_dimension`
|
||||
|
||||
`units::merge_dimension` is similar to `make_dimension` but instead of sorting the whole list
|
||||
of base dimensions from scratch it assumes that provided input `dimension` types are already
|
||||
sorted as a result of `make_dimension`.
|
||||
|
||||
Typical use case for `merge_dimension` is to produce final `dimension` return type of multiplying
|
||||
two different dimensions:
|
||||
|
||||
```cpp
|
||||
template<Dimension D1, Dimension D2>
|
||||
struct dimension_multiply;
|
||||
|
||||
template<Exponent... E1, Exponent... E2>
|
||||
struct dimension_multiply<dimension<E1...>, dimension<E2...>> {
|
||||
using type = upcasting_traits_t<merge_dimension_t<dimension<E1...>, dimension<E2...>>>;
|
||||
};
|
||||
|
||||
template<Dimension D1, Dimension D2>
|
||||
using dimension_multiply_t = typename dimension_multiply<typename D1::base_type, typename D2::base_type>::type;
|
||||
```
|
||||
|
||||
Example implementation of `merge_dimension` may look like:
|
||||
|
||||
```cpp
|
||||
template<Dimension D1, Dimension D2>
|
||||
struct merge_dimension {
|
||||
using type = mp::type_list_sort_t<detail::dim_consolidate_t<mp::type_list_merge_sorted_t<D1, D2, exp_dim_id_less>>, exp_greater_equal>;
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
### `Units`
|
||||
|
||||
`units::unit` is a class template that expresses the unit of a specific physical dimension:
|
||||
|
||||
```cpp
|
||||
template<Dimension D, Ratio R>
|
||||
requires (R::num > 0)
|
||||
struct unit : upcast_base<unit<D, R>> {
|
||||
using dimension = D;
|
||||
using ratio = R;
|
||||
};
|
||||
```
|
||||
|
||||
`units::Unit` is a Concept that is satisfied by a type that is empty and publicly
|
||||
derived from `units::unit` class template:
|
||||
|
||||
```cpp
|
||||
template<typename T>
|
||||
concept Unit =
|
||||
std::is_empty_v<T> &&
|
||||
detail::is_unit<typename T::base_type> && // exposition only
|
||||
DerivedFrom<T, typename T::base_type>;
|
||||
```
|
||||
|
||||
### `Quantities`
|
||||
|
||||
`units::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, Unit U, Number Rep>
|
||||
requires Same<D, typename U::dimension>
|
||||
class quantity;
|
||||
```
|
||||
|
||||
`units::Quantity` is a Concept that is satisfied by a type that is an instantiation of `units::quantity`
|
||||
class template:
|
||||
|
||||
```cpp
|
||||
template<typename T>
|
||||
concept Quantity =
|
||||
detail::is_quantity<T>; // exposition only
|
||||
```
|
||||
|
||||
`units::quantity` provides the interface really similar to `std::chrono::duration` with additional
|
||||
member types and functions as below:
|
||||
|
||||
```cpp
|
||||
template<Dimension D, Unit U, Number Rep>
|
||||
requires Same<D, typename U::dimension>
|
||||
class quantity {
|
||||
public:
|
||||
using dimension = D;
|
||||
using unit = U;
|
||||
|
||||
template<Dimension D1, Unit U1, Number Rep1, Dimension D2, Unit U2, Number Rep2>
|
||||
requires treat_as_floating_point<std::common_type_t<Rep1, Rep2>> || std::ratio_multiply<typename U1::ratio, typename U2::ratio>::den == 1
|
||||
quantity<dimension_multiply_t<D1, D2>, upcasting_traits_t<unit<dimension_multiply_t<D1, D2>, std::ratio_multiply<typename U1::ratio, typename U2::ratio>>>, std::common_type_t<Rep1, Rep2>>
|
||||
constexpr operator*(const quantity<D1, U1, Rep1>& lhs,
|
||||
const quantity<D2, U2, Rep2>& rhs);
|
||||
|
||||
template<Number Rep1, Dimension D, Unit U, Number Rep2>
|
||||
quantity<dim_invert_t<D>, upcasting_traits_t<unit<dim_invert_t<D>, std::ratio<U::ratio::den, U::ratio::num>>>, std::common_type_t<Rep1, Rep2>>
|
||||
constexpr operator/(const Rep1& v,
|
||||
const quantity<D, U, Rep2>& q);
|
||||
|
||||
template<Dimension D1, Unit U1, Number Rep1, Dimension D2, Unit U2, Number Rep2>
|
||||
requires treat_as_floating_point<std::common_type_t<Rep1, Rep2>> || std::ratio_divide<typename U1::ratio, typename U2::ratio>::den == 1
|
||||
quantity<dimension_divide_t<D1, D2>, upcasting_traits_t<unit<dimension_divide_t<D1, D2>, std::ratio_divide<typename U1::ratio, typename U2::ratio>>>, std::common_type_t<Rep1, Rep2>>
|
||||
constexpr 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.
|
||||
|
||||
#### `quantity_cast`
|
||||
|
||||
To explicitly force truncating conversions `quantity_cast` function is provided which is a direct
|
||||
counterpart of `std::chrono::duration_cast`.
|
||||
|
||||
## Strong types instead of aliases, and type upcasting capability
|
||||
|
||||
Most of the important design decisions in the library are dictated by the requirement of providing
|
||||
the best user experience as possible.
|
||||
|
||||
For example with template aliases usage the following code:
|
||||
|
||||
```cpp
|
||||
const Velocity t = 20_s;
|
||||
```
|
||||
|
||||
could generate a following compile time error:
|
||||
|
||||
```text
|
||||
C:\repos\units\example\example.cpp:39:22: error: deduced initializer does not satisfy placeholder constraints
|
||||
const Velocity t = 20_s;
|
||||
^~~~
|
||||
In file included from C:\repos\units\example\example.cpp:23:
|
||||
C:/repos/units/src/include/units/si/velocity.h:41:16: note: within 'template<class T> concept const bool units::Velocity<T> [with T = units::quantity<units::dimension<units::exp<units::base_dim_time, 1> >, units::unit<units::dimension<units::exp<units::base_dim_time, 1> >, std::ratio<1> >, long long int>]'
|
||||
concept Velocity = Quantity<T> && Same<typename T::dimension, dimension_velocity>;
|
||||
^~~~~~~~
|
||||
In file included from C:/repos/units/src/include/units/bits/tools.h:25,
|
||||
from C:/repos/units/src/include/units/dimension.h:25,
|
||||
from C:/repos/units/src/include/units/si/base_dimensions.h:25,
|
||||
from C:/repos/units/src/include/units/si/velocity.h:25,
|
||||
from C:\repos\units\example\example.cpp:23:
|
||||
C:/repos/units/src/include/units/bits/stdconcepts.h:33:18: note: within 'template<class T, class U> concept const bool mp::std_concepts::Same<T, U> [with T = units::dimension<units::exp<units::base_dim_time, 1> >; U = units::dimension<units::exp<units::base_dim_length, 1>, units::exp<units::base_dim_time, -1> >]'
|
||||
concept Same = std::is_same_v<T, U>;
|
||||
^~~~
|
||||
C:/repos/units/src/include/units/bits/stdconcepts.h:33:18: note: 'std::is_same_v' evaluated to false
|
||||
```
|
||||
|
||||
Time and velocity are not that complicated dimensions and there are much more complicated dimensions
|
||||
out there, but even for those dimensions
|
||||
|
||||
```text
|
||||
[with T = units::quantity<units::dimension<units::exp<units::base_dim_time, 1> >, units::unit<units::dimension<units::exp<units::base_dim_time, 1> >, std::ratio<1> >, long long int>]
|
||||
```
|
||||
|
||||
and
|
||||
|
||||
```text
|
||||
[with T = units::dimension<units::exp<units::base_dim_time, 1> >; U = units::dimension<units::exp<units::base_dim_length, 1>, units::exp<units::base_dim_time, -1> >]
|
||||
```
|
||||
|
||||
starts to be really hard to analyze or debug.
|
||||
|
||||
That is why it was decided to provide automated upcasting capability when possible. With that the
|
||||
same code will result with such an error:
|
||||
|
||||
```text
|
||||
C:\repos\units\example\example.cpp:40:22: error: deduced initializer does not satisfy placeholder constraints
|
||||
const Velocity t = 20_s;
|
||||
^~~~
|
||||
In file included from C:\repos\units\example\example.cpp:23:
|
||||
C:/repos/units/src/include/units/si/velocity.h:48:16: note: within 'template<class T> concept const bool units::Velocity<T> [with T = units::quantity<units::dimension_time, units::second, long long int>]'
|
||||
concept Velocity = Quantity<T> && Same<typename T::dimension, dimension_velocity>;
|
||||
^~~~~~~~
|
||||
In file included from C:/repos/units/src/include/units/bits/tools.h:25,
|
||||
from C:/repos/units/src/include/units/dimension.h:25,
|
||||
from C:/repos/units/src/include/units/si/base_dimensions.h:25,
|
||||
from C:/repos/units/src/include/units/si/velocity.h:25,
|
||||
from C:\repos\units\example\example.cpp:23:
|
||||
C:/repos/units/src/include/units/bits/stdconcepts.h:33:18: note: within 'template<class T, class U> concept const bool mp::std_concepts::Same<T, U> [with T = units::dimension_time; U = units::dimension_velocity]'
|
||||
concept Same = std::is_same_v<T, U>;
|
||||
^~~~
|
||||
C:/repos/units/src/include/units/bits/stdconcepts.h:33:18: note: 'std::is_same_v' evaluated to false
|
||||
```
|
||||
|
||||
Now
|
||||
|
||||
```text
|
||||
[with T = units::quantity<units::dimension_time, units::second, long long int>]
|
||||
```
|
||||
|
||||
and
|
||||
|
||||
```text
|
||||
[with T = units::dimension_time; U = units::dimension_velocity]
|
||||
```
|
||||
|
||||
are not arguably much easier to understand thus provide better user experience.
|
||||
|
||||
Upcasting capability is provided through dedicated `upcasting_traits` and by `base_type` member
|
||||
type in `upcast_base` class template.
|
||||
|
||||
```cpp
|
||||
template<typename T>
|
||||
struct upcasting_traits : std::type_identity<T> {};
|
||||
|
||||
template<typename T>
|
||||
using upcasting_traits_t = typename upcasting_traits<T>::type;
|
||||
```
|
||||
|
||||
```cpp
|
||||
struct dimension_length : make_dimension_t<exp<base_dim_length, 1>> {};
|
||||
|
||||
template<>
|
||||
struct upcasting_traits<typename dimension_length::base_type> :
|
||||
std::type_identity<dimension_length> {};
|
||||
```
|
||||
|
||||
```cpp
|
||||
struct kilometer : unit<dimension_length, std::kilo> {};
|
||||
|
||||
template<>
|
||||
struct upcasting_traits<typename kilometer::base_type> :
|
||||
std::type_identity<kilometer> {};
|
||||
```
|
||||
|
||||
|
||||
## Adding new dimensions
|
||||
|
||||
In order to extend the library with custom dimensions the user has to:
|
||||
1. Create a new dimension type and provide upcasting trait for it:
|
||||
|
||||
```cpp
|
||||
struct dimension_velocity : make_dimension_t<exp<base_dim_length, 1>, exp<base_dim_time, -1>> {};
|
||||
template<> struct upcasting_traits<typename dimension_velocity::base_type> : std::type_identity<dimension_velocity> {};
|
||||
```
|
||||
|
||||
2. Define the base unit (`std::ratio<1>`) and secondary ones and provide upcasting traits for them via:
|
||||
|
||||
```cpp
|
||||
struct meter_per_second : unit<dimension_velocity, std::ratio<1>> {};
|
||||
template<> struct upcasting_traits<typename meter_per_second::base_type> : std::type_identity<meter_per_second> {};
|
||||
```
|
||||
|
||||
3. Define a concept that will match a new dimension:
|
||||
|
||||
```cpp
|
||||
template<typename T>
|
||||
concept Velocity = Quantity<T> && Same<typename T::dimension, dimension_velocity>;
|
||||
```
|
||||
|
||||
4. Provide user-defined literals for the most important units:
|
||||
|
||||
```cpp
|
||||
namespace literals {
|
||||
constexpr auto operator""_mps(unsigned long long l) { return velocity<meter_per_second, std::int64_t>(l); }
|
||||
constexpr auto operator""_mps(long double l) { return velocity<meter_per_second, long double>(l); }
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Adding new base dimensions
|
||||
|
||||
For now base dimensions are defined in terms of `std::integral_constant<int, ...>` and the provided
|
||||
values must be unique. For example:
|
||||
|
||||
```cpp
|
||||
struct base_dim_length : dim_id<0> {};
|
||||
struct base_dim_mass : dim_id<1> {};
|
||||
struct base_dim_time : dim_id<2> {};
|
||||
struct base_dim_electric_current : dim_id<3> {};
|
||||
struct base_dim_temperature : dim_id<4> {};
|
||||
struct base_dim_amount_of_substance : dim_id<5> {};
|
||||
struct base_dim_luminous_intensity : dim_id<6> {};
|
||||
```
|
||||
|
||||
However, as soon as C++20 class type values will be supported as non-type template parameters
|
||||
base dimensions will be just a text values. For example:
|
||||
|
||||
```cpp
|
||||
inline constexpr base_dim base_dim_length = "length";
|
||||
```
|
||||
|
||||
With that it should be really easy to add support for any new non-standard base units to the
|
||||
library without the risk of collision with any dimension type defined by the library itself or
|
||||
by other users extending the library with their own dimension types.
|
||||
|
||||
Additionally, it should make the error logs even shorter thus easier to understand.
|
||||
|
||||
|
||||
## Open questions
|
||||
|
||||
1. Should we ensure that dimension is always a result of `make_dimension`? How to do it?
|
||||
|
||||
2. Should we provide strong types and upcasting_traits for `quantity` type?
|
||||
|
||||
In such a case all the operators have to be provided to a child class. Or maybe use CRTP?
|
||||
|
||||
3. What to do with `time` which is ambiguous (conflict wit ANSI C)?
|
||||
|
||||
4. What to do with `std::chrono::duration`? Is it possible to make it derive from
|
||||
`quantity<dimension_time, U, Rep>` which will most probably an ABI break? Alternatively,
|
||||
should we provide specialization of `quantity<dimension_time, U, Rep>` to work with/covnert
|
||||
from/to `std::duration`?
|
||||
|
||||
5. Should we provide `seconds<int>` or stay with `time<second, int>`? What about CTAD problem
|
||||
for `units::length<units::mile> d3(3);`?
|
||||
|
||||
6. What is the best way to add support for temperatures?
|
||||
|
||||
Temperatures not only require `std::ratio` but also should be adjusted/shifted by some
|
||||
constant values (i.e. [°C] = [K] − 273.15).
|
||||
|
||||
7. Should we use `units::multiply` or stay with `std::ratio` for multiplication?
|
||||
|
||||
8. Should we consider making `units::multiply` and `units::offset` a non-class template parameters
|
||||
as they provide different ratio values rather than types?
|
||||
|
||||
In example instead:
|
||||
|
||||
```cpp
|
||||
struct celsius : unit<dimension_temperature, convert<offset<-27315, 100>>> {};
|
||||
```
|
||||
|
||||
we could think about something like:
|
||||
|
||||
```cpp
|
||||
struct celsius : unit<dimension_temperature, kelvin() - 27315/100>>> {};
|
||||
```
|
||||
|
||||
9. Do we need non-linear scale?
|
||||
|
||||
10. Should we provide cmath-like functions for quantities?
|
||||
|
||||
11. What should be the resulting type of `auto d = 1_km + 1_ft;`?
|
||||
|
||||
12. Should we require explicit casts (i.e. quantity_cast) between different systems of
|
||||
measurement?
|
||||
|
||||
13. Should we provide Boost-like support for a `quantity_cast` to a reference that allows
|
||||
direct access to the underlying value of a quantity variable?
|
||||
|
||||
14. What should be the default representation (integral or `double`)?
|
||||
|
||||
15. Provide ostream overloads to print quantity units (use `std::format`)?
|
||||
|
||||
16. Should we provide support for dimensionless quantities?
|
||||
|
||||
Because dimensionless quantities have no associated units, they behave as normal scalars,
|
||||
and allow implicit conversion to and from the underlying value type or types that are
|
||||
convertible to/from that value type.
|
96
doc/INSTALL.md
Normal file
96
doc/INSTALL.md
Normal file
@ -0,0 +1,96 @@
|
||||
# Installation and Reuse
|
||||
|
||||
There are a few different ways of installing/reusing `units` in your project
|
||||
|
||||
## 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. They can be easily obtained with conan
|
||||
|
||||
```python
|
||||
requires = (
|
||||
"cmcstl2/2019.03.18@mpusz/stable",
|
||||
"gsl-lite/0.33.0@nonstd-lite/stable"
|
||||
)
|
||||
```
|
||||
|
||||
## cmake
|
||||
|
||||
To use `units` as a `cmake` imported library via `cmake` configuration files the following
|
||||
steps may be done.
|
||||
|
||||
### cmake install
|
||||
|
||||
```bash
|
||||
$ mkdir build && cd build
|
||||
$ cmake ../src -DCMAKE_INSTALL_PREFIX=<your_cmake_installation_dir> <your_cmake_configuration>
|
||||
$ cmake --build . --target install <your_cmake_configuration>
|
||||
```
|
||||
|
||||
To use such `cmake` target in your project it is enough to add following line to your
|
||||
`CMakeList.txt` file
|
||||
|
||||
```cmake
|
||||
find_package(units CONFIG REQUIRED)
|
||||
```
|
||||
|
||||
and configure it with
|
||||
|
||||
```bash
|
||||
$ cmake .. -DCMAKE_INSTALL_PREFIX=<your_cmake_installation_dir> <your_cmake_configuration>
|
||||
```
|
||||
|
||||
### cmake + conan
|
||||
|
||||
To use `units` with `cmake` via `conan` it is enough to:
|
||||
- add the following remotes to your local `conan` instance
|
||||
|
||||
```bash
|
||||
$ conan remote add conan-mpusz https://bintray.com/mpusz/conan-mpusz
|
||||
$ conan remote add conan-nonstd https://api.bintray.com/conan/martinmoene/nonstd-lite
|
||||
```
|
||||
|
||||
- add the following dependency to your `conanfile.txt` or `conanfile.py` files
|
||||
|
||||
```python
|
||||
requires = "units/0.0.1@mpusz/testing"
|
||||
```
|
||||
|
||||
- install conan dependencies before configuring cmake
|
||||
|
||||
```bash
|
||||
$ cd build
|
||||
$ conan install .. -pr <your_conan_profile> -b=outdated -u
|
||||
```
|
||||
|
||||
|
||||
# Full build and unit testing
|
||||
|
||||
In case you would like to build all the code in that repository (with unit tests and examples)
|
||||
you should use `CMakeLists.txt` from the parent directory.
|
||||
|
||||
```bash
|
||||
mkdir build && cd build
|
||||
conan install .. <your_profile_and_settings>
|
||||
cmake .. <your_cmake_configuration>
|
||||
cmake --build .
|
||||
```
|
||||
|
||||
|
||||
# Packaging
|
||||
|
||||
To create a `conan` package and test `cmake` installation and `conan` packaging run:
|
||||
|
||||
```bash
|
||||
$ conan create . <username>/<channel> --build=outdated <your_profile_and_settings>
|
||||
```
|
||||
|
||||
|
||||
# Upload package to conan server
|
||||
|
||||
```bash
|
||||
$ conan upload -r <remote-name> --all units/0.0.1@<user>/<channel>
|
||||
```
|
@ -20,23 +20,9 @@
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
cmake_minimum_required(VERSION 3.8)
|
||||
project(units_example)
|
||||
|
||||
# set path to custom cmake modules
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../cmake/common/cmake")
|
||||
|
||||
# include common tools and workarounds
|
||||
include(tools)
|
||||
|
||||
# add dependencies
|
||||
if(NOT TARGET mp::units)
|
||||
find_package(units CONFIG REQUIRED)
|
||||
endif()
|
||||
|
||||
# example app
|
||||
add_executable(example example.cpp)
|
||||
target_link_libraries(example
|
||||
PRIVATE
|
||||
mp::units
|
||||
units::units
|
||||
)
|
||||
|
@ -23,26 +23,40 @@
|
||||
#include "units/si/velocity.h"
|
||||
#include <iostream>
|
||||
|
||||
using namespace units;
|
||||
namespace {
|
||||
|
||||
template<Velocity V, Time T>
|
||||
void foo(V v, T t)
|
||||
using namespace units::literals;
|
||||
|
||||
template<units::Length D, units::Time T>
|
||||
constexpr units::Velocity avg_speed(D d, T t)
|
||||
{
|
||||
const Length distance = v * t;
|
||||
std::cout << "A car driving " << v.count() << " km/h in a time of " << t.count() << " minutes will pass "
|
||||
<< quantity_cast<length<meter, double>>(distance).count() << " meters.\n";
|
||||
return d / t;
|
||||
}
|
||||
|
||||
void foo()
|
||||
template<units::Velocity V, units::Time T>
|
||||
void example_1(V v, T t)
|
||||
{
|
||||
using namespace units::literals;
|
||||
foo(60_kmph, 10.0_min);
|
||||
const units::Length distance = v * t;
|
||||
std::cout << "A car driving " << v.count() << " km/h in a time of " << t.count() << " minutes will pass "
|
||||
<< units::quantity_cast<units::length<units::meter, double>>(distance).count() << " meters.\n";
|
||||
}
|
||||
|
||||
void example_2(double distance_v, double duration_v)
|
||||
{
|
||||
units::length<units::kilometer> distance(distance_v);
|
||||
units::time<units::hour> duration(duration_v);
|
||||
const auto kmph = avg_speed(distance, duration);
|
||||
std::cout << "Average speed of a car that makes " << distance.count() << " km in "
|
||||
<< duration.count() << " hours is " << kmph.count() << " km/h.\n";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
try {
|
||||
foo();
|
||||
example_1(60_kmph, 10.0_min);
|
||||
example_2(220, 2);
|
||||
}
|
||||
catch (const std::exception& ex) {
|
||||
std::cerr << "Unhandled std exception caught: " << ex.what() << '\n';
|
||||
|
@ -21,9 +21,11 @@
|
||||
# SOFTWARE.
|
||||
|
||||
cmake_minimum_required(VERSION 3.8)
|
||||
#cmake_policy(SET CMP0076 NEW)
|
||||
|
||||
project(units
|
||||
VERSION 0.0.1
|
||||
LANGUAGES C CXX
|
||||
LANGUAGES CXX
|
||||
)
|
||||
|
||||
# set path to custom cmake modules
|
||||
@ -32,20 +34,21 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../cmake/common/cmake
|
||||
# include common tools and workarounds
|
||||
include(tools)
|
||||
|
||||
# add dependencies
|
||||
# find_package(...)
|
||||
|
||||
# library definition
|
||||
add_library(units INTERFACE)
|
||||
#target_sources(units INTERFACE
|
||||
# include/units/units.h
|
||||
# include/units/length.h
|
||||
# include/units/time.h
|
||||
# include/units/dimension.h
|
||||
# include/units/quantity.h
|
||||
# include/units/unit.h
|
||||
#
|
||||
# include/units/bits/common_type.h
|
||||
# include/units/bits/dimensions.h
|
||||
# include/units/bits/quantity.h
|
||||
# include/units/bits/tools.h
|
||||
# include/units/bits/type_list.h
|
||||
#
|
||||
# include/units/si/base_dimensions.h
|
||||
# include/units/si/frequency.h
|
||||
# include/units/si/length.h
|
||||
# include/units/si/time.h
|
||||
# include/units/si/velocity.h
|
||||
#)
|
||||
target_compile_features(units INTERFACE cxx_std_20)
|
||||
target_link_libraries(units
|
||||
@ -59,7 +62,7 @@ target_include_directories(units
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
|
||||
$<INSTALL_INTERFACE:include>
|
||||
)
|
||||
add_library(mp::units ALIAS units)
|
||||
add_library(units::units ALIAS units)
|
||||
|
||||
# installation info
|
||||
install(TARGETS units EXPORT ${CMAKE_PROJECT_NAME}Targets
|
||||
@ -74,4 +77,4 @@ install(DIRECTORY include/units
|
||||
)
|
||||
|
||||
# generate configuration files and install the package
|
||||
configure_and_install(../cmake/common/cmake/simple_package-config.cmake.in SameMajorVersion)
|
||||
configure_and_install(../cmake/common/cmake/simple_package-config.cmake.in units SameMajorVersion)
|
||||
|
@ -20,21 +20,6 @@
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
cmake_minimum_required(VERSION 3.8)
|
||||
project(units_test)
|
||||
|
||||
# set path to custom cmake modules
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../cmake/common/cmake")
|
||||
|
||||
# include common tools and workarounds
|
||||
include(tools)
|
||||
|
||||
# add dependencies
|
||||
enable_testing()
|
||||
if(NOT TARGET mp::units)
|
||||
find_package(units CONFIG REQUIRED)
|
||||
endif()
|
||||
|
||||
# unit tests
|
||||
add_library(unit_tests
|
||||
test_dimension.cpp
|
||||
@ -45,9 +30,5 @@ add_library(unit_tests
|
||||
)
|
||||
target_link_libraries(unit_tests
|
||||
PRIVATE
|
||||
mp::units
|
||||
)
|
||||
add_test(NAME units.unit_tests
|
||||
COMMAND
|
||||
unit_tests
|
||||
units::units
|
||||
)
|
||||
|
40
test_package/CMakeLists.txt
Normal file
40
test_package/CMakeLists.txt
Normal file
@ -0,0 +1,40 @@
|
||||
# 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.
|
||||
|
||||
cmake_minimum_required(VERSION 2.8.12)
|
||||
project(test_package)
|
||||
|
||||
set(CMAKE_VERBOSE_MAKEFILE TRUE)
|
||||
|
||||
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
|
||||
conan_basic_setup(TARGETS)
|
||||
|
||||
find_package(units CONFIG REQUIRED)
|
||||
|
||||
# test conan-generated target
|
||||
add_executable(${PROJECT_NAME}_conan test_package.cpp)
|
||||
target_compile_features(${PROJECT_NAME}_conan PRIVATE cxx_std_20) # conan is not able to propagate that yet :-(
|
||||
target_link_libraries(${PROJECT_NAME}_conan PRIVATE CONAN_PKG::units)
|
||||
|
||||
# test cmake target
|
||||
add_executable(${PROJECT_NAME}_cmake test_package.cpp)
|
||||
target_link_libraries(${PROJECT_NAME}_cmake PRIVATE units::units)
|
52
test_package/conanfile.py
Normal file
52
test_package/conanfile.py
Normal file
@ -0,0 +1,52 @@
|
||||
# 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.
|
||||
|
||||
from conans import ConanFile, CMake, tools, RunEnvironment
|
||||
import os
|
||||
|
||||
class TestPackageConan(ConanFile):
|
||||
settings = "os", "compiler", "build_type", "arch"
|
||||
generators = "cmake"
|
||||
|
||||
def build(self):
|
||||
cmake = CMake(self)
|
||||
cmake.configure()
|
||||
cmake.build()
|
||||
|
||||
def imports(self):
|
||||
self.copy("*.dll", dst="bin", src="bin")
|
||||
self.copy("*.dylib*", dst="bin", src="lib")
|
||||
self.copy('*.so*', dst='bin', src='lib')
|
||||
|
||||
def _test_run(self, bin_path):
|
||||
if self.settings.os == "Windows":
|
||||
self.run(bin_path)
|
||||
elif self.settings.os == "Macos":
|
||||
self.run("DYLD_LIBRARY_PATH=%s %s" % (os.environ.get('DYLD_LIBRARY_PATH', ''), bin_path))
|
||||
else:
|
||||
self.run("LD_LIBRARY_PATH=%s %s" % (os.environ.get('LD_LIBRARY_PATH', ''), bin_path))
|
||||
|
||||
def test(self):
|
||||
if not tools.cross_building(self.settings):
|
||||
with tools.environment_append(RunEnvironment(self).vars):
|
||||
self._test_run(os.path.join("bin", "test_package_conan"))
|
||||
self._test_run(os.path.join("bin", "test_package_cmake"))
|
37
test_package/test_package.cpp
Normal file
37
test_package/test_package.cpp
Normal file
@ -0,0 +1,37 @@
|
||||
// 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.
|
||||
|
||||
#include <units/si/velocity.h>
|
||||
#include <iostream>
|
||||
|
||||
using namespace units::literals;
|
||||
|
||||
template<units::Length D, units::Time T>
|
||||
constexpr units::Velocity avg_speed(D d, T t)
|
||||
{
|
||||
return d / t;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
std::cout << "Average speed = " << avg_speed(240.0_km, 2_h).count() << " kmph\n";
|
||||
}
|
Reference in New Issue
Block a user