==================================================================
* // ) ) /__ ___/ // ) ) // ) ) /| / / // / / *
* (( / / //___/ / // / / //| / / //__ / / *
* \\ / / / ___ ( // / / // | / / //__ / *
* ) ) / / // | | // / / // | / / // \ \ *
* ((___ / / / / // | | ((___/ / // |/ / // \ \ *
==================================================================
- Easy support for stron{g|k} typing, with plenty of built-in skills to add functionality to your own types.
- Automatically combine types with physics-like unit behavior:
10 [Meter] / 2 [Second] = 5 [Meter / Second]
.
- Strong types allow you to catch argument ordering mismatches.
- Unit-like behavior allows you to use the type system to verify the correctness of your implementation.
- Catch refactoring bugs at compile time by limiting access to the underlying values.
#include <iostream>
#include <string>
#include <stronk/can_stream.h>
#include <stronk/stronk.h>
struct FirstName : twig::stronk<FirstName, std::string, twig::can_ostream>
{
using stronk::stronk;
};
struct LastName : twig::stronk<LastName, std::string>
{
using stronk::stronk;
};
// Strong types protects you from accidentally passing the wrong argument to the wrong parameter.
void print_name(const LastName& lastname, const FirstName& firstname)
{
// The twig::can_ostream skill overloads the `operator<<(ostream&)` for your type.
std::cout << firstname << " ";
// You can also access the underlying type by using the .unwrap<Type>() function.
std::cout << lastname.unwrap<LastName>() << std::endl;
}
auto main() -> int
{
print_name(LastName {"Doe"}, FirstName {"John"});
}
On top of providing strong type utilities, stronk
also enables unit-like behavior:
#include <ratio>
#include <type_traits>
#include <stronk/prefabs.h>
#include <stronk/stronk.h>
#include <stronk/unit.h>
// We introduce a unit type with a default set of skills with the `stronk_default_unit` prefab
struct Watt : twig::stronk_default_unit<Watt, double>
{
using stronk_default_unit::stronk_default_unit;
};
void watts_and_identity_units()
{
Watt watt = Watt {30.};
watt += Watt {4.} - Watt {2.}; // we can add and subtract units
// Multiplying and dividing with an identity_unit (such as floats and integers) does not change the type.
watt *= 2.;
// However an identity_unit divided by a regular unit results in a new unit type.
auto one_over_watt = 1.0 / watt;
static_assert(!std::is_same_v<decltype(one_over_watt), Watt>);
}
Different units can be combined by multiplying or dividing them:
// Lets introduce hours as a new unit_like type
struct Hours : twig::stronk<Hours, double, twig::unit>
{
using stronk::stronk;
};
// We can now dynamically generate a new type!
using WattHours = decltype(Watt {} * Hours {});
void watt_hours_and_generating_new_units()
{
// Multiplying the right units together will automatically produce the new type
WattHours watt_hours = Hours {3.} * Watt {25.};
// The new type supports adding, subtracting, comparing etc by default.
watt_hours -= WattHours {10.} + WattHours {2.};
// We can get back to Hours or Watt by dividing the opposite out.
Hours hours = watt_hours / Watt {25.};
Watt watt = watt_hours / Hours {3.};
}
These new generated types are also units which can be used to generate new units:
// Lets introduce a type for euros, and start combining more types.
struct Euro : twig::stronk<Euro, double, twig::unit>
{
using stronk::stronk;
};
void introducing_another_type()
{
// twig::make allows you to scale the input value but it does not change the resulting type
WattHours one_mega_watt_hour = twig::make<std::mega, WattHours>(1.);
// Now we can generate a new type which consists of 3 types: `Euro / (Watt * Hours)`
auto euros_per_mega_watt_hour = Euro {300.} / one_mega_watt_hour;
// This flexibility allows us to write expessive code, while having the type system check our implementation.
Euro price_for_buying_5_mega_watt_hours = euros_per_mega_watt_hour * twig::make<std::mega, WattHours>(5.);
auto mega_watt_hours_per_euro = 1. / euros_per_mega_watt_hour; // `(Watt * Hours) / Euro`
WattHours mega_watt_hours_affordable_for_500_euros = mega_watt_hours_per_euro * Euro {500.};
}
Units are a great way of using the type system to validate your code.
Credit to Jonathan Müller's blogpost and Jonathan Boccara's blogpost - both of which have been great sources of inspiration.
Skills adds functionality to your stronk types. We have implemented a number of generic skills which should help you get started.
can_negate
: unaryoperator-
can_add
: binaryoperator+
operator=+
can_subtract
: binaryoperator-
andoperator=-
can_multiply
: binaryoperator*
andoperator=*
(not compatible with units, we encourage you to use units instead)can_divide
: binaryoperator/
andoperator=/
(not compatible with units, we encourage you to use units instead)can_abs
: overloadstwig::abs
can_isnan
: overloadstwig::isnan
can_stream
: overloadsoperator<<(std::ostream)
andoperator<<(std::istream)
, stream the underlying value to the stream, or create from stream. For onlyostream
oristream
functionality, usecan_ostream
orcan_istream
respectively.can_order
:operator<=>
, note you probably also want to addcan_equate
, since the compiler cannot generate equality with theoperator<=>
for stronk types.can_equate
:operator==
with regular equalitycan_equate_with_is_close
:operator==
but with numpy'sis_close
definition of equalcan_equate_with_is_close_nan_equals
:operator==
but with numpy'sis_close
definition of equal, nans being equalcan_equate_with_is_close_abs_tol_only
:operator==
with a small absolute tolerance for differencecan_less_than_greater_than
:operator<
andoperator>
(prefer thecan_order
skill instead)can_less_than_greater_than_or_equal
: operator <= and operator >= (prefer thecan_order
skill instead)can_be_used_as_flag
: for boolean values used as flagscan_hash
: specializesstd::hash<T>
.can_size
: implements.size()
and.empty()
can_const_iterate
implementsbegin() const
,end() const
,cbegin() const
andcend() const
.can_iterate
adds thecan_const_iterate
as well implementingbegin()
,end()
.can_const_index
implementsoperator[](const auto&) const
andat(const auto&) const
can_index
adds thecan_const_index
as well implementingoperator[](const auto&)
andat(const auto&)
.can_increment
adds bothoperator++
operators.can_decrement
adds bothoperator--
operators.
unit
: enables unit behavior for multiplication and division.identity_unit
: enables unit behavior, but does not affect the type of multiplication and division.
can_absl_hash
: implements theAbslHashValue
friend function.can_gtest_print
: for printing the values in gtest check macroscan_fmt_format
: implementsstruct fmt::formatter<T>
with default formatting string"{}"
. In the future we will add acan_format
forstd::format
.can_fmt_format_builder<"fmt format string{}">::skill
: implementsstruct fmt::formatter<T>
. In the future we will add acan_format_builder<"std format string">
forstd::format
.
Adding new skills is easy so feel free to add more.
Often you might just need a group of skills for your specific types. For this you can use prefabs.
stronk_default_unit
: arithmetic unit with most of the regular operationsstronk_flag
: flag like boolean with equal operators etc.
In case you want to specialize the resulting type of unit multiplication and division you can utilize the stronk/specializer.h
header.
By default the units are generated with the stronk_default_prefab
type.
#include <cstdint>
#include <type_traits>
#include <stronk/stronk.h>
#include <stronk/unit.h>
// Lets consider the following units:
struct Distance : twig::stronk<Distance, double, twig::unit>
{
using stronk::stronk;
};
struct Time : twig::stronk<Time, double, twig::unit>
{
using stronk::stronk;
};
// Lets say you want to use a custom defined stronk type for certain unit combinations.
// Lets introduce our own `Speed` type:
struct Speed : twig::stronk<Speed, double, twig::divided_unit<Distance, Time>::skill>
{
using stronk::stronk;
};
// Notice we are adding the twig::divided_unit skill instead of twig::unit
// To make it possible for stronk to find this type we need to specialize `unit_lookup`:
template<>
struct twig::unit_lookup<twig::divided_unit<Distance, Time>::dimensions_t, double>
{
using type = Speed;
};
// The above of course also works for `multiplied_unit` and `unit_multiplied_resulting_unit_type`
The project is CMake FetchContent ready and is available on vcpkg. After retrieving stronk, add the following to your CMakeLists.txt
find_package(stronk CONFIG REQUIRED)
target_link_libraries(
project_target PRIVATE
twig::stronk
)
A c++20 compatible compiler and standard library with concepts support.
We depend on Boost's type_index package to get compile time generated ids for each type to be able to sort types for units (so we can compare types generated from different expressions).
In the extensions subfolder we have added skills for common third party libraries: fmt
, absl
and gtest
. Using these also requires the relevant third party libraries to be installed.
For more information on how to build see the BUILDING and HACKING documents.
Stronk is a close to zero cost abstraction - performance varies per compiler and we get the best results when running with clang-13. Unfortunately the performance with MSVC is quite bad. We are investigating the issue, and initial results points to padding of the stronk structures being the root cause. You can see benchmark results for all the tested platforms in the Continuous Integration Workflow.
Constructing and copying the structs performs identically or very close to identically with just passing the raw types:
2022-09-06T15:29:32+02:00
Running ./build/dev/benchmarks/stronk_benchmarks
Run on (64 X 4549.12 MHz CPU s)
CPU Caches:
L1 Data 32 KiB (x32)
L1 Instruction 32 KiB (x32)
L2 Unified 512 KiB (x32)
L3 Unified 16384 KiB (x8)
Load Average: 5.12, 10.46, 10.65
***WARNING*** CPU scaling is enabled, the benchmark real time measurements may be noisy and will incur extra overhead.
------------------------------------------------------------------------------------------------------------------------------
Benchmark Time CPU Iterations
------------------------------------------------------------------------------------------------------------------------------
benchmark_default_onto_reserved_vector<int8_t> /32 8.91 ns 8.91 ns 78563433
benchmark_default_onto_reserved_vector<int8_t_wrapping_type> /32 8.66 ns 8.66 ns 80918037
benchmark_default_onto_reserved_vector<int8_t> /8192 1980 ns 1979 ns 353087
benchmark_default_onto_reserved_vector<int8_t_wrapping_type> /8192 1986 ns 1986 ns 354193
benchmark_default_onto_reserved_vector<int64_t> /32 8.90 ns 8.90 ns 78708349
benchmark_default_onto_reserved_vector<int64_t_wrapping_type> /32 9.09 ns 9.09 ns 80903274
benchmark_default_onto_reserved_vector<int64_t> /8192 2045 ns 2044 ns 343621
benchmark_default_onto_reserved_vector<int64_t_wrapping_type> /8192 2037 ns 2036 ns 343414
benchmark_default_onto_reserved_vector<std::string> /32 47.8 ns 47.8 ns 14663125
benchmark_default_onto_reserved_vector<string_wrapping_type> /32 85.5 ns 85.5 ns 8131930
benchmark_default_onto_reserved_vector<std::string> /8192 19631 ns 19624 ns 36104
benchmark_default_onto_reserved_vector<string_wrapping_type> /8192 22198 ns 22190 ns 31634
benchmark_rand_onto_reserved_vector<int8_t> /32 158 ns 158 ns 4422920
benchmark_rand_onto_reserved_vector<int8_t_wrapping_type> /32 161 ns 161 ns 4321957
benchmark_rand_onto_reserved_vector<int8_t> /8192 39296 ns 39287 ns 18003
benchmark_rand_onto_reserved_vector<int8_t_wrapping_type> /8192 41177 ns 41165 ns 13780
benchmark_rand_onto_reserved_vector<int64_t> /32 492 ns 491 ns 1341406
benchmark_rand_onto_reserved_vector<int64_t_wrapping_type> /32 474 ns 474 ns 1457581
benchmark_rand_onto_reserved_vector<int64_t> /8192 118668 ns 118618 ns 6019
benchmark_rand_onto_reserved_vector<int64_t_wrapping_type> /8192 117694 ns 117661 ns 5962
benchmark_rand_onto_reserved_vector<std::string> /32 21612 ns 21605 ns 32047
benchmark_rand_onto_reserved_vector<string_wrapping_type> /32 21155 ns 21149 ns 32177
benchmark_rand_onto_reserved_vector<std::string> /8192 5583745 ns 5581594 ns 126
benchmark_rand_onto_reserved_vector<string_wrapping_type> /8192 5520208 ns 5518877 ns 125
benchmark_copy_vector_of<int8_t> /32 11.0 ns 11.0 ns 61066804
benchmark_copy_vector_of<int8_t_wrapping_type> /32 11.8 ns 11.8 ns 58053187
benchmark_copy_vector_of<int8_t> /8192 100.0 ns 99.9 ns 6840525
benchmark_copy_vector_of<int8_t_wrapping_type> /8192 114.0 ns 114 ns 6107469
benchmark_copy_vector_of<int64_t> /32 11.8 ns 11.8 ns 59132687
benchmark_copy_vector_of<int64_t_wrapping_type> /32 13.0 ns 13.0 ns 54934171
benchmark_copy_vector_of<int64_t> /8192 1070 ns 1070 ns 653306
benchmark_copy_vector_of<int64_t_wrapping_type> /8192 1067 ns 1067 ns 657525
benchmark_copy_vector_of<std::string> /32 522 ns 522 ns 1336216
benchmark_copy_vector_of<string_wrapping_type> /32 528 ns 528 ns 1319940
benchmark_copy_vector_of<std::string> /8192 206292 ns 206209 ns 3501
benchmark_copy_vector_of<string_wrapping_type> /8192 207750 ns 207709 ns 3368
Calling "Skill" functions (which internally calls unwrap) performs identically to calling the functions directly on the raw types. They even auto-vectorize the same:
2022-09-06T15:29:32+02:00
Running ./build/dev/benchmarks/stronk_benchmarks
Run on (64 X 4549.12 MHz CPU s)
CPU Caches:
L1 Data 32 KiB (x32)
L1 Instruction 32 KiB (x32)
L2 Unified 512 KiB (x32)
L3 Unified 16384 KiB (x8)
Load Average: 5.12, 10.46, 10.65
***WARNING*** CPU scaling is enabled, the benchmark real time measurements may be noisy and will incur extra overhead.
--------------------------------------------------------------------------------------------------------------------------------
Benchmark Time CPU Iterations
--------------------------------------------------------------------------------------------------------------------------------
benchmark_add_units<int8_t> /8192 4114 ns 4113 ns 172898
benchmark_add_units<int8_t_wrapping_type> /8192 4074 ns 4073 ns 172007
benchmark_add_units<int64_t> /8192 4059 ns 4058 ns 172825
benchmark_add_units<int64_t_wrapping_type> /8192 4136 ns 4135 ns 172749
benchmark_add_units<double> /8192 4168 ns 4167 ns 171310
benchmark_add_units<double_wrapping_type> /8192 4203 ns 4202 ns 170063
benchmark_add_units_simd<int8_t, 32> /256 284 ns 284 ns 2482647
benchmark_add_units_simd<int8_t_wrapping_type, 32> /256 278 ns 278 ns 2521970
benchmark_add_units_simd<int64_t, 32> /256 1804 ns 1802 ns 386445
benchmark_add_units_simd<int64_t_wrapping_type, 32> /256 1802 ns 1801 ns 387243
benchmark_add_units_simd<double, 32> /256 1812 ns 1811 ns 386561
benchmark_add_units_simd<double_wrapping_type, 32> /256 1791 ns 1790 ns 388848
benchmark_subtract_units<int8_t> /8192 4129 ns 4128 ns 172555
benchmark_subtract_units<int8_t_wrapping_type> /8192 4042 ns 4041 ns 172917
benchmark_subtract_units<int64_t> /8192 4016 ns 4015 ns 174396
benchmark_subtract_units<int64_t_wrapping_type> /8192 4012 ns 4011 ns 174147
benchmark_subtract_units<double> /8192 4030 ns 4029 ns 173898
benchmark_subtract_units<double_wrapping_type> /8192 4019 ns 4018 ns 174130
benchmark_subtract_units_simd<int8_t, 32> /256 278 ns 278 ns 2528806
benchmark_subtract_units_simd<int8_t_wrapping_type, 32> /256 278 ns 278 ns 2522150
benchmark_subtract_units_simd<int64_t, 32> /256 1796 ns 1796 ns 388739
benchmark_subtract_units_simd<int64_t_wrapping_type, 32> /256 1797 ns 1797 ns 389213
benchmark_subtract_units_simd<double, 32> /256 1817 ns 1817 ns 386436
benchmark_subtract_units_simd<double_wrapping_type, 32> /256 1806 ns 1806 ns 384036
benchmark_multiply_units<int8_t, int8_t> /8192 4840 ns 4840 ns 144495
benchmark_multiply_units<int8_t_wrapping_type, int8_t_wrapping_type> /8192 4836 ns 4835 ns 144600
benchmark_multiply_units<int64_t, int64_t> /8192 4176 ns 4175 ns 168529
benchmark_multiply_units<int64_t_wrapping_type, int64_t_wrapping_type> /8192 4144 ns 4144 ns 167954
benchmark_multiply_units<double, double> /8192 4032 ns 4031 ns 173870
benchmark_multiply_units<double_wrapping_type, double_wrapping_type> /8192 4029 ns 4027 ns 172664
benchmark_multiply_units<int64_t, double> /8192 4044 ns 4043 ns 173192
benchmark_multiply_units<int64_t_wrapping_type, double_wrapping_type> /8192 4040 ns 4039 ns 173067
benchmark_multiply_units<double, int64_t> /8192 4036 ns 4034 ns 173323
benchmark_multiply_units<double_wrapping_type, int64_t_wrapping_type> /8192 3990 ns 3989 ns 172120
benchmark_multiply_units_simd<int8_t, int8_t, 32> /256 2162 ns 2161 ns 323603
benchmark_multiply_units_simd<int8_t_wrapping_type, int8_t_wrapping_type, 32> /256 2168 ns 2167 ns 323478
benchmark_multiply_units_simd<int64_t, int64_t, 32> /256 2756 ns 2755 ns 256899
benchmark_multiply_units_simd<int64_t_wrapping_type, int64_t_wrapping_type, 32> /256 2731 ns 2730 ns 255456
benchmark_multiply_units_simd<double, double, 32> /256 1835 ns 1835 ns 381571
benchmark_multiply_units_simd<double_wrapping_type, double_wrapping_type, 32> /256 1826 ns 1826 ns 386378
benchmark_multiply_units_simd<int64_t, double, 32> /256 3938 ns 3937 ns 181518
benchmark_multiply_units_simd<int64_t_wrapping_type, double_wrapping_type, 32> /256 3922 ns 3921 ns 174342
benchmark_multiply_units_simd<double, int64_t, 32> /256 3979 ns 3978 ns 175656
benchmark_multiply_units_simd<double_wrapping_type, int64_t_wrapping_type, 32> /256 3973 ns 3972 ns 175886
benchmark_divide_units<int8_t, int8_t> /8192 28624 ns 28615 ns 24461
benchmark_divide_units<int8_t_wrapping_type, int8_t_wrapping_type> /8192 28646 ns 28639 ns 24440
benchmark_divide_units<int64_t, int64_t> /8192 28736 ns 28725 ns 24393
benchmark_divide_units<int64_t_wrapping_type, int64_t_wrapping_type> /8192 28449 ns 28444 ns 24369
benchmark_divide_units<double, double> /8192 10116 ns 10114 ns 69634
benchmark_divide_units<double_wrapping_type, double_wrapping_type> /8192 10175 ns 10173 ns 69232
benchmark_divide_units<int64_t, double> /8192 10280 ns 10277 ns 69438
benchmark_divide_units<int64_t_wrapping_type, double_wrapping_type> /8192 9991 ns 9988 ns 69697
benchmark_divide_units<double, int64_t> /8192 10079 ns 10077 ns 69349
benchmark_divide_units<double_wrapping_type, int64_t_wrapping_type> /8192 10043 ns 10038 ns 69509
benchmark_divide_units_simd<int8_t, int8_t, 32> /256 25015 ns 25007 ns 28410
benchmark_divide_units_simd<int8_t_wrapping_type, int8_t_wrapping_type, 32> /256 24891 ns 24884 ns 28246
benchmark_divide_units_simd<int64_t, int64_t, 32> /256 24910 ns 24905 ns 28098
benchmark_divide_units_simd<int64_t_wrapping_type, int64_t_wrapping_type, 32> /256 24888 ns 24881 ns 27959
benchmark_divide_units_simd<double, double, 32> /256 4362 ns 4361 ns 160498
benchmark_divide_units_simd<double_wrapping_type, double_wrapping_type, 32> /256 4371 ns 4370 ns 159631
benchmark_divide_units_simd<int64_t, double, 32> /256 4363 ns 4362 ns 160427
benchmark_divide_units_simd<int64_t_wrapping_type, double_wrapping_type, 32> /256 4366 ns 4365 ns 160489
benchmark_divide_units_simd<double, int64_t, 32> /256 4365 ns 4363 ns 160532
benchmark_divide_units_simd<double_wrapping_type, int64_t_wrapping_type, 32> /256 4372 ns 4371 ns 160567
See the LICENSE document.