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
|
*.app
|
||||||
|
|
||||||
# IDE-specific
|
# IDE-specific
|
||||||
.*/
|
.idea/
|
||||||
cmake-build-*/
|
.vscode/
|
||||||
|
*build*/
|
||||||
|
|
||||||
# Conan
|
# Conan
|
||||||
*.pyc
|
*.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_subdirectory(src)
|
||||||
|
|
||||||
# add unit tests
|
# add unit tests
|
||||||
enable_testing()
|
|
||||||
add_subdirectory(test)
|
add_subdirectory(test)
|
||||||
|
|
||||||
# add usage example
|
# add usage example
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
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
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
458
README.md
458
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++
|
# `units` - Physical Units Library for C++
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
`Units` is a compile-time enabled Modern C++ library that provides support for converting physical
|
`Units` is a compile-time enabled Modern C++ library that provides compile-time dimensional
|
||||||
units and dimensions. The basic idea and design heavily bases on `std::chrono::duration` and extends
|
analysis and unit/quantity manipulation. The basic idea and design heavily bases on
|
||||||
it to work properly with many dimensions.
|
`std::chrono::duration` and extends it to work properly with many dimensions.
|
||||||
|
|
||||||
Here is a small example of possible operations:
|
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);
|
static_assert(2_km / 2_kmph == 1_h);
|
||||||
```
|
```
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
1. Safety and performance
|
## Repository structure
|
||||||
- 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
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
Please note that all projects depend on some `cmake` modules in `./cmake` directory.
|
||||||
|
|
||||||
## Basic Concepts
|
|
||||||
|
|
||||||
### `Dimensions`
|
## Building, testing and installation
|
||||||
|
|
||||||
`units::dimension` is a type-list like type that stores an ordered list of exponents of one
|
For detailed information on project compilation, testing and reuse please refer to
|
||||||
or more base dimensions:
|
[doc/INSTALL.md](doc/INSTALL.md).
|
||||||
|
|
||||||
```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
|
## Library design
|
||||||
derived from `units::dimension` class template:
|
|
||||||
|
|
||||||
```cpp
|
`units` library design rationale and documentation can be found in
|
||||||
template<typename T>
|
[doc/DESIGN.md](doc/DESIGN.md)
|
||||||
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?
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
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"
|
name = "units"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
author = "Mateusz Pusz"
|
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"
|
url = "https://github.com/mpusz/units"
|
||||||
description = "Physical Units library for C++"
|
description = "Physical Units library for C++"
|
||||||
|
exports = ["LICENSE.md"]
|
||||||
settings = "os", "compiler", "build_type", "arch"
|
settings = "os", "compiler", "build_type", "arch"
|
||||||
requires = (
|
requires = (
|
||||||
"cmcstl2/2019.03.18@mpusz/stable",
|
"cmcstl2/2019.03.18@mpusz/stable",
|
||||||
"gsl-lite/0.33.0@nonstd-lite/stable"
|
"gsl-lite/0.33.0@nonstd-lite/stable"
|
||||||
)
|
)
|
||||||
|
scm = {
|
||||||
|
"type": "git",
|
||||||
|
"url": "auto",
|
||||||
|
"revision": "auto"
|
||||||
|
}
|
||||||
generators = "cmake"
|
generators = "cmake"
|
||||||
|
|
||||||
def build(self):
|
def _configure_cmake(self):
|
||||||
cmake = CMake(self)
|
cmake = CMake(self)
|
||||||
cmake.configure(source_dir="%s/src" % self.source_folder)
|
cmake.configure(source_dir="%s/src" % self.source_folder)
|
||||||
|
return cmake
|
||||||
|
|
||||||
|
def build(self):
|
||||||
|
cmake = self._configure_cmake()
|
||||||
cmake.build()
|
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):
|
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
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
# SOFTWARE.
|
# 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
|
# example app
|
||||||
add_executable(example example.cpp)
|
add_executable(example example.cpp)
|
||||||
target_link_libraries(example
|
target_link_libraries(example
|
||||||
PRIVATE
|
PRIVATE
|
||||||
mp::units
|
units::units
|
||||||
)
|
)
|
||||||
|
@ -23,26 +23,40 @@
|
|||||||
#include "units/si/velocity.h"
|
#include "units/si/velocity.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
using namespace units;
|
namespace {
|
||||||
|
|
||||||
template<Velocity V, Time T>
|
using namespace units::literals;
|
||||||
void foo(V v, T t)
|
|
||||||
|
template<units::Length D, units::Time T>
|
||||||
|
constexpr units::Velocity avg_speed(D d, T t)
|
||||||
{
|
{
|
||||||
const Length distance = v * t;
|
return d / 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";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void foo()
|
template<units::Velocity V, units::Time T>
|
||||||
|
void example_1(V v, T t)
|
||||||
{
|
{
|
||||||
using namespace units::literals;
|
const units::Length distance = v * t;
|
||||||
foo(60_kmph, 10.0_min);
|
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()
|
int main()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
foo();
|
example_1(60_kmph, 10.0_min);
|
||||||
|
example_2(220, 2);
|
||||||
}
|
}
|
||||||
catch (const std::exception& ex) {
|
catch (const std::exception& ex) {
|
||||||
std::cerr << "Unhandled std exception caught: " << ex.what() << '\n';
|
std::cerr << "Unhandled std exception caught: " << ex.what() << '\n';
|
||||||
|
@ -21,9 +21,11 @@
|
|||||||
# SOFTWARE.
|
# SOFTWARE.
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.8)
|
cmake_minimum_required(VERSION 3.8)
|
||||||
|
#cmake_policy(SET CMP0076 NEW)
|
||||||
|
|
||||||
project(units
|
project(units
|
||||||
VERSION 0.0.1
|
VERSION 0.0.1
|
||||||
LANGUAGES C CXX
|
LANGUAGES CXX
|
||||||
)
|
)
|
||||||
|
|
||||||
# set path to custom cmake modules
|
# 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 common tools and workarounds
|
||||||
include(tools)
|
include(tools)
|
||||||
|
|
||||||
# add dependencies
|
|
||||||
# find_package(...)
|
|
||||||
|
|
||||||
# library definition
|
# library definition
|
||||||
add_library(units INTERFACE)
|
add_library(units INTERFACE)
|
||||||
#target_sources(units INTERFACE
|
#target_sources(units INTERFACE
|
||||||
# include/units/units.h
|
# include/units/dimension.h
|
||||||
# include/units/length.h
|
# include/units/quantity.h
|
||||||
# include/units/time.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/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_compile_features(units INTERFACE cxx_std_20)
|
||||||
target_link_libraries(units
|
target_link_libraries(units
|
||||||
@ -59,7 +62,7 @@ target_include_directories(units
|
|||||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
|
||||||
$<INSTALL_INTERFACE:include>
|
$<INSTALL_INTERFACE:include>
|
||||||
)
|
)
|
||||||
add_library(mp::units ALIAS units)
|
add_library(units::units ALIAS units)
|
||||||
|
|
||||||
# installation info
|
# installation info
|
||||||
install(TARGETS units EXPORT ${CMAKE_PROJECT_NAME}Targets
|
install(TARGETS units EXPORT ${CMAKE_PROJECT_NAME}Targets
|
||||||
@ -74,4 +77,4 @@ install(DIRECTORY include/units
|
|||||||
)
|
)
|
||||||
|
|
||||||
# generate configuration files and install the package
|
# 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
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
# SOFTWARE.
|
# 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
|
# unit tests
|
||||||
add_library(unit_tests
|
add_library(unit_tests
|
||||||
test_dimension.cpp
|
test_dimension.cpp
|
||||||
@ -45,9 +30,5 @@ add_library(unit_tests
|
|||||||
)
|
)
|
||||||
target_link_libraries(unit_tests
|
target_link_libraries(unit_tests
|
||||||
PRIVATE
|
PRIVATE
|
||||||
mp::units
|
units::units
|
||||||
)
|
|
||||||
add_test(NAME units.unit_tests
|
|
||||||
COMMAND
|
|
||||||
unit_tests
|
|
||||||
)
|
)
|
||||||
|
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