units building and packaging redesigned + Travis CI support

This commit is contained in:
Mateusz Pusz
2019-04-06 23:25:35 +02:00
parent 09457e9b8f
commit 8d86d9401f
18 changed files with 888 additions and 501 deletions

5
.gitignore vendored
View File

@ -29,8 +29,9 @@
*.app *.app
# IDE-specific # IDE-specific
.*/ .idea/
cmake-build-*/ .vscode/
*build*/
# Conan # Conan
*.pyc *.pyc

34
.travis.yml Normal file
View 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
View 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
View 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

View File

@ -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

View File

@ -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
View File

@ -1,10 +1,15 @@
[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?maxAge=3600)](https://raw.githubusercontent.com/mpusz/units/master/LICENSE)
[![Travis CI](https://img.shields.io/travis/mpusz/units/master.svg?label=Travis%20CI)](https://travis-ci.org/mpusz/units)
[![AppVeyor](https://img.shields.io/appveyor/ci/mpusz/units/master.svg?label=AppVeyor)](https://ci.appveyor.com/project/mpusz/units)
[![Download](https://api.bintray.com/packages/mpusz/conan-mpusz/units%3Ampusz/images/download.svg)](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
View 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()

View File

@ -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
View 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
View 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>
```

View File

@ -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
) )

View File

@ -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';

View File

@ -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)

View File

@ -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
) )

View 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
View 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"))

View 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";
}