Skip to content

Commit e606477

Browse files
committed
Adding the C++20 header only version
1 parent e9a30c8 commit e606477

File tree

9 files changed

+310
-4
lines changed

9 files changed

+310
-4
lines changed

generator_code/targets/cppDesktop.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
],
3838
"file_patters": [
3939
"include/unit_system.hpp",
40+
"include/unit_system_20.hpp",
4041
"src/*.cpp"
4142
]
4243
},

generator_code/targets/meson.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
],
3838
"file_patters": [
3939
"include/unit_system.hpp",
40+
"include/unit_system_20.hpp",
4041
"src/*.cpp"
4142
]
4243
},
Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
{% extends "generic/unit_system.template_local" %}
2+
3+
{% block include_area %}
4+
{{ super() }}
5+
6+
#if __cplusplus < 202002L
7+
#error "C++ 20 support is required"
8+
#endif
9+
10+
11+
#ifndef UNIT_SYSTEM_DEFAULT_TYPE
12+
#define UNIT_SYSTEM_DEFAULT_TYPE long double
13+
#endif
14+
15+
#include <algorithm>
16+
#include <chrono>
17+
#include <cmath>
18+
#include <concepts>
19+
#include <iostream>
20+
#include <ratio>
21+
22+
#ifndef UNIT_SYSTEM_EXPORT_MACRO
23+
#define UNIT_SYSTEM_EXPORT_MACRO
24+
#endif
25+
{%endblock%}
26+
27+
{% block base_class_area %}
28+
namespace sakurajin{
29+
namespace unit_system{
30+
31+
{% for unit in units %}
32+
template <std::floating_point base_type>
33+
class {{ unit.name }}_t {
34+
private:
35+
base_type value{static_cast<base_type>(0.0)};
36+
base_type multiplier{static_cast<base_type>(1.0)};
37+
base_type offset{static_cast<base_type>(0.0)};
38+
base_type rel_error{static_cast<base_type>(0.000001)};
39+
40+
public:
41+
{{ unit.name }}_t() = default;
42+
{{ unit.name }}_t(const {{ unit.name }}_t& other) = default;
43+
44+
template <class value_t = base_type, class mult_t = base_type, class offset_t = base_type>
45+
requires std::convertible_to<value_t, base_type> && std::convertible_to<mult_t, base_type> && std::convertible_to<offset_t, base_type>
46+
explicit {{ unit.name }}_t(value_t v, mult_t mult = 1.0, offset_t off = 0.0)
47+
: value{static_cast<base_type>(v)}, multiplier{static_cast<base_type>(mult)}, offset{static_cast<base_type>(off)} {}
48+
49+
template<std::intmax_t numerator, std::intmax_t denumerator = 1, class value_t = base_type, class offset_t = base_type>
50+
explicit {{ unit.name }}_t(value_t v, std::ratio<numerator, denumerator>, offset_t off = 0.0): {{unit.name}}_t{
51+
v, static_cast<long double>(numerator)/static_cast<long double>(denumerator), off
52+
}{}
53+
54+
55+
base_type val() const { return value; }
56+
base_type mult() const { return multiplier; }
57+
base_type off() const { return offset; }
58+
base_type rel_err() const { return rel_error; }
59+
60+
base_type& val() { return value; }
61+
base_type& mult() { return multiplier; }
62+
base_type& off() { return offset; }
63+
base_type& rel_err() { return rel_error; }
64+
65+
66+
template <class scalar_t> requires std::convertible_to<scalar_t, base_type>
67+
{{ unit.name }}_t<base_type> operator*(scalar_t scalar) const {
68+
return {{ unit.name }}_t<base_type>{value * static_cast<base_type>(scalar), multiplier, offset};
69+
};
70+
71+
template <class scalar_t> requires std::convertible_to<scalar_t, base_type>
72+
void operator*=(scalar_t scalar) { value *= static_cast<base_type>(scalar); }
73+
74+
template <class scalar_t> requires std::convertible_to<scalar_t, base_type>
75+
{{ unit.name }}_t<base_type> operator/(scalar_t scalar) const {
76+
return {{ unit.name }}_t<base_type>{value / static_cast<base_type>(scalar), multiplier, offset};
77+
}
78+
template <class scalar_t> requires std::convertible_to<scalar_t, base_type>
79+
void operator/=(scalar_t scalar) { value /= static_cast<base_type>(scalar); }
80+
81+
82+
long double operator/(const {{ unit.name }}_t& other) const {
83+
return static_cast<long double>(this->value) / static_cast<long double>(other.convert_like(*this).val());
84+
}
85+
86+
{{ unit.name }}_t operator+(const {{ unit.name }}_t& other) const {
87+
auto retval = other.convert_like(*this);
88+
retval.val() += this->value;
89+
return retval;
90+
}
91+
void operator+=(const {{ unit.name }}_t& other) {
92+
auto retval = other.convert_like(*this);
93+
this->value += retval.val();
94+
}
95+
96+
{{ unit.name }}_t operator-(const {{ unit.name }}_t& other) const {
97+
auto retval = this->convert_like(other);
98+
retval.val() -= other.val();
99+
return retval;
100+
}
101+
void operator-=(const {{ unit.name }}_t& other) {
102+
auto retval = other.convert_like(*this);
103+
this->value -= retval.val();
104+
}
105+
106+
{{ unit.name }}_t operator-() const { return {{ unit.name }}_t{-value, multiplier, offset}; }
107+
108+
{{ unit.name }}_t& operator=(const {{ unit.name }}_t& other) = default;
109+
110+
explicit operator long double() const{
111+
return convert_copy(1.0, 0.0).val();
112+
}
113+
114+
template <class mult_t = base_type, class offset_t = base_type>
115+
requires std::convertible_to<mult_t, base_type> && std::convertible_to<offset_t, base_type>
116+
[[nodiscard]]{{ unit.name }}_t convert_copy(mult_t new_multiplier, offset_t new_offset) const {
117+
auto new_mult = static_cast<base_type>(new_multiplier);
118+
auto new_off = static_cast<base_type>(new_offset);
119+
auto new_val = (value * multiplier + (offset - new_off)) / new_mult;
120+
return {{ unit.name }}_t{new_val, new_mult, new_off};
121+
}
122+
123+
template <class mult_t = base_type>
124+
requires std::convertible_to<mult_t, base_type>
125+
[[nodiscard]]
126+
auto convert_multiplier(mult_t new_multiplier) const {
127+
return convert_copy(new_multiplier, this->offset);
128+
}
129+
130+
template <class offset_t = base_type>
131+
requires std::convertible_to<offset_t, base_type>
132+
[[nodiscard]]
133+
auto convert_offset(offset_t new_offset) const {
134+
return convert_copy(this->multiplier, new_offset);
135+
}
136+
137+
// returns a copy of the unit with the same multiplier and offset as the other unit
138+
[[nodiscard]]
139+
auto convert_like(const {{ unit.name }}_t& other) const {
140+
return convert_copy(other.mult(), other.off());
141+
}
142+
143+
auto operator<=>(const {{ unit.name }}_t& other) const { return this->val() <=> other.convert_like(*this).val(); }
144+
145+
auto operator==(const {{ unit.name }}_t& other) const { return this->val() == other.convert_like(*this).val(); }
146+
auto operator!=(const {{ unit.name }}_t& other) const { return this->val() != other.convert_like(*this).val(); }
147+
};
148+
149+
150+
template <std::floating_point base_type, class sclar_t>
151+
requires std::convertible_to<sclar_t, base_type>
152+
{{ unit.name }}_t<base_type> operator*(sclar_t scalar, const {{ unit.name }}_t<base_type>& value) {
153+
return value * scalar;
154+
}
155+
156+
template <std::floating_point base_type, class mult_t = base_type, class offset_t = base_type>
157+
requires std::convertible_to<mult_t, base_type> && std::convertible_to<offset_t, base_type>
158+
{{ unit.name }}_t<base_type> unit_cast(const {{ unit.name }}_t<base_type>& unit, mult_t new_multiplier = 1, offset_t new_offset = 0) {
159+
return unit.convert_copy(new_multiplier, new_offset);
160+
}
161+
162+
template <std::floating_point base_type>
163+
{{ unit.name }}_t<base_type> clamp(const {{ unit.name }}_t<base_type>& unit, const {{ unit.name }}_t<base_type>& lower, const {{ unit.name }}_t<base_type>& upper) {
164+
auto low = lower.convert_like(unit);
165+
auto high = upper.convert_like(unit);
166+
return {{ unit.name }}_t<base_type>{std::clamp(unit.val(), low.val(), high.val()), unit.mult(), unit.off()};
167+
}
168+
169+
template <std::floating_point base_type>
170+
std::ostream& operator<<(std::ostream& os, const {{ unit.name }}_t<base_type>& val) {
171+
auto val_raw = val.convert_copy(1.0, 0.0);
172+
return os << val_raw.val() << " {{ base_name }}";
173+
}
174+
175+
{% endfor %}
176+
177+
//define all unit combination operators
178+
{% for unit in units %}
179+
180+
{% if unit.divisions|length > 0 %}{% for div in unit.divisions %}
181+
template <std::floating_point base_type>
182+
[[nodiscard]]auto operator/(const {{ unit.name }}_t<base_type>& val, const {{ div.divisor }}_t<base_type>& other){
183+
auto _v1 = val.convert_offset(0);
184+
auto _v2 = other.convert_offset(0);
185+
return sakurajin::unit_system::{{ div.result }}_t<base_type>{_v1.val()/_v2.val(),_v1.mult()/_v2.mult()};
186+
}
187+
{% endfor %}{% endif %}
188+
189+
{% if unit.multiplications|length > 0 %}{% for mult in unit.multiplications %}
190+
template <std::floating_point base_type>
191+
[[nodiscard]]auto operator*(const {{ unit.name }}_t<base_type>& val, const {{ mult.factor }}_t<base_type>& other){
192+
auto _v1 = val.convert_offset(0);
193+
auto _v2 = other.convert_offset(0);
194+
return sakurajin::unit_system::{{ mult.product }}_t<base_type>{_v1.val()*_v2.val(),_v1.mult()/_v2.mult()};
195+
}
196+
{% endfor %}{% endif %}
197+
198+
{% if unit.square_result != '' %}
199+
template <std::floating_point base_type>
200+
[[nodiscard]]auto square(const {{ unit.name }}_t<base_type>& val) {return val*val;}{% endif %}
201+
202+
{% if unit.sqrt_result != '' %}
203+
template <std::floating_point base_type>
204+
[[nodiscard]]auto sqrt(const {{ unit.name }}_t<base_type>& val){
205+
return sakurajin::unit_system::{{ unit.sqrt_result }}_t<base_type>{ {{ extra_data.sqrt_function }}(val.val()), {{ extra_data.sqrt_function }}(val.mult()), val.off()};
206+
}
207+
{% endif %}
208+
209+
{% endfor %}
210+
211+
//forward declare all units
212+
{% for unit in units %}
213+
UNIT_SYSTEM_EXPORT_MACRO typedef {{ unit.name }}_t<UNIT_SYSTEM_DEFAULT_TYPE> {{ unit.name }};
214+
{% endfor %}
215+
216+
217+
//define all literals
218+
inline namespace literals{
219+
{% for unit in units %}
220+
{% if unit.literals|length > 0 %}
221+
{% for literal in unit.literals %}
222+
{{ extra_data.export_macro }} auto operator "" _{{ literal.code_literal }}(long double val){
223+
return sakurajin::unit_system::{{ unit.name }}{val,{{ literal.multiplier }}, {{ literal.offset }}};
224+
}
225+
{{ extra_data.export_macro }} auto operator "" _{{ literal.code_literal }}(unsigned long long int val){
226+
return sakurajin::unit_system::{{ unit.name }}{static_cast<long double>(val),{{ literal.multiplier }}, {{ literal.offset }}};
227+
}
228+
{% endfor %}
229+
{% endif %}{% endfor %}
230+
}
231+
}
232+
}
233+
{%endblock%}
234+
235+
{% block std_compat %}{% if extra_data.has_std %}
236+
namespace std{
237+
{% for unit in units %}
238+
template <std::floating_point base_type>
239+
sakurajin::unit_system::{{ unit.name }}_t<base_type> abs(const sakurajin::unit_system::{{ unit.name }}_t<base_type>& unit){
240+
const auto raw_val = unit.val();
241+
const auto inv_val = -raw_val;
242+
const auto abs_val = (raw_val > inv_val) ? raw_val : inv_val;
243+
return sakurajin::unit_system::{{ unit.name }}_t<base_type>{abs_val, unit.mult(), unit.off()};
244+
}
245+
{% endfor %}
246+
}
247+
248+
249+
//add compatibility with std::chrono
250+
namespace sakurajin{
251+
namespace unit_system{
252+
template<class Rep, class Period = std::ratio<1> >
253+
time_si unit_cast(const std::chrono::duration<Rep, Period>& other, auto new_multiplier = 1.0){
254+
auto t = std::chrono::duration_cast<std::chrono::duration<double, std::ratio<1,1>>>(other);
255+
auto retval = time_si{t.count(), 1};
256+
return retval.convert_multiplier(new_multiplier);
257+
}
258+
}
259+
}
260+
{% endif %}{% endblock %}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#include <gtest/gtest.h>
2+
3+
#include "test_functions_20.hpp"
4+
5+
#include "base_units_test.cpp"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#include <gtest/gtest.h>
2+
3+
#include "test_functions_20.hpp"
4+
5+
#include "common_test.cpp"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#include <gtest/gtest.h>
2+
3+
#include "unit_system_20.hpp"
4+
5+
#define EXPECT_UNIT_EQ(X, Y) EXPECT_DOUBLE_EQ((X).val(), (Y).convert_like(X).val())
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#include <gtest/gtest.h>
2+
3+
#include "test_functions_20.hpp"
4+
5+
#include "unit_test.cpp"

target_data/Cpp/meson/meson.build.template

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,18 @@ unit_system_dep = declare_dependency(
4242
version: meson.project_version(),
4343
)
4444

45+
unit_system_20_dep = declare_dependency(
46+
include_directories : incdirs,
47+
version: meson.project_version(),
48+
)
49+
4550
pkg = import('pkgconfig')
4651
pkg.generate(unit_system, subdirs: 'unit_system')
4752

4853
install_headers('include/unit_system.hpp')
4954

5055
meson.override_dependency('unit-system', unit_system_dep)
56+
meson.override_dependency('unit-system-20', unit_system_20_dep)
5157

5258
build_tests = get_option('build_tests').enable_auto_if(not meson.is_subproject())
5359
if build_tests.enabled()

target_data/Cpp/meson/tests/meson.build

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ tests_17 = [
77
'common_test_17',
88
]
99

10-
tests = [
11-
'unit_test',
12-
'base_units_test',
13-
'common_test',
10+
tests_20 = [
11+
'unit_test_20',
12+
'base_units_test_20',
13+
'common_test_20',
1414
]
1515

1616
test_incdir = include_directories('.')
@@ -33,3 +33,21 @@ foreach test_obj : tests_17
3333
protocol: 'gtest',
3434
)
3535
endforeach
36+
37+
foreach test_obj : tests_20
38+
deps = [
39+
dependency('unit-system-20', required: true),
40+
gtest_dep,
41+
]
42+
exe = executable(
43+
test_obj,
44+
test_obj + '.cpp',
45+
dependencies: deps,
46+
include_directories : test_incdir,
47+
)
48+
test(
49+
test_obj + ' Test',
50+
exe,
51+
protocol: 'gtest',
52+
)
53+
endforeach

0 commit comments

Comments
 (0)