Skip to content

Commit

Permalink
More tests, better specializers and bump version to v0.2.0 (#25)
Browse files Browse the repository at this point in the history
* Trying to add a move unwrap

* Bump version

* Improve multiply and divide specializers

* Updated README.md

* Fix README with new specializer example length

* Added specializer tests

* Better move tests

* Reverted the && overload but keept the tests

* Try to use checkout v3

Co-authored-by: anders-wind <[email protected]>
  • Loading branch information
anders-wind and anders-wind authored Sep 8, 2022
1 parent 687d6b9 commit f298bca
Show file tree
Hide file tree
Showing 10 changed files with 176 additions and 40 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-22.04

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3

- uses: actions/setup-python@v2
with: { python-version: "3.8" }
Expand All @@ -46,7 +46,7 @@ jobs:
if: github.repository_owner == 'twig-energy'

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3

- name: Install LCov
run: sudo apt-get update -q
Expand Down Expand Up @@ -81,7 +81,7 @@ jobs:
runs-on: ubuntu-22.04

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3

- name: Install vcpkg
uses: friendlyanon/setup-vcpkg@v1
Expand Down Expand Up @@ -127,7 +127,7 @@ jobs:
runs-on: ${{ matrix.os }}

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3

- name: Install static analyzers
if: matrix.os == 'ubuntu-22.04'
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ include(cmake/prelude.cmake)

project(
stronk
VERSION 0.1.0
VERSION 0.2.0
DESCRIPTION "An easy to customize strong type library with built in support for unit-like behavior"
HOMEPAGE_URL "https://github.com/twig-energy/stronk"
LANGUAGES NONE
Expand Down
24 changes: 19 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ In case you want to specialize the resulting type of unit multiplication and div

By default the units are generated with the `stronk_default_prefab` type.

```cpp :file=./examples/specializers_example.cpp:line_end=22
```cpp :file=./examples/specializers_example.cpp:line_end=36
#include <stronk/specializers.h>

// Lets consider the following units:
Expand All @@ -211,6 +211,20 @@ STRONK_SPECIALIZE_DIVIDE(Distance, Time, can_hash);
// Now any expression resulting the `Distance{} / Time{}` type will result in a unit type with the can_hash skill

} // namespace twig

// Lets specialize Time^2 to use int64_t as its underlying type.
template<>
struct twig::underlying_multiply_operation<Time, Time>
{
using res_type = int64_t;

STRONK_FORCEINLINE
constexpr static auto multiply(const typename Time::underlying_type& v1,
const typename Time::underlying_type& v2) noexcept -> res_type
{
return static_cast<int64_t>(v1 * v2);
}
};
```
You can also specialize the underlying type of multiplying two units:
Expand All @@ -219,10 +233,10 @@ By default the `underlying_type` is the default result of multiplying or dividin
```cpp :file=./examples/specializers_example.cpp:line_start=23:line_end=29
// Lets specialize Time^2 to use int64_t as its underlying type.
template<>
struct twig::underlying_type_of_multiplying<Time, Time>
struct twig::underlying_multiply_operation<Time, Time>
{
using type = int64_t;
};
using res_type = int64_t;
```

# Using Stronk in Your Project
Expand All @@ -249,7 +263,7 @@ In the extensions subfolder we have added skills for common third party librarie
For more information on how to build see the [BUILDING](BUILDING.md) and [HACKING](HACKING.md) documents.

# Benchmarks
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](https://github.com/twig-energy/stronk/issues/24), 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](https://github.com/twig-energy/stronk/actions/workflows/ci.yml).
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](https://github.com/twig-energy/stronk/issues/24), 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](https://github.com/twig-energy/stronk/actions/workflows/ci.yml).

Constructing and copying the structs performs identically or very close to identically with just passing the raw types:
```
Expand Down
13 changes: 10 additions & 3 deletions examples/specializers_example.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,16 @@ STRONK_SPECIALIZE_DIVIDE(Distance, Time, can_hash);

// Lets specialize Time^2 to use int64_t as its underlying type.
template<>
struct twig::underlying_type_of_multiplying<Time, Time>
struct twig::underlying_multiply_operation<Time, Time>
{
using type = int64_t;
using res_type = int64_t;

STRONK_FORCEINLINE
constexpr static auto multiply(const typename Time::underlying_type& v1,
const typename Time::underlying_type& v2) noexcept -> res_type
{
return static_cast<int64_t>(v1 * v2);
}
};

auto main() -> int
Expand All @@ -34,4 +41,4 @@ auto main() -> int
auto speed_hash = std::hash<Speed> {}(Speed {25.});
static_assert((Time {2.} * Time {4.}).unwrap<decltype(Time {} * Time {})>() == 8ULL);
}
static_assert(__LINE__ == 37UL, "update readme if this changes");
static_assert(__LINE__ == 44UL, "update readme if this changes");
12 changes: 6 additions & 6 deletions include/stronk/specializers.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@
// Macro for getting the full type of multiplying two units
// Only works when T1 and T2 has one unit on the multiplication part
// NOLINTNEXTLINE
#define STRONK_MULTIPLY_TYPE(T1, T2) \
NewUnitType<typename twig::underlying_type_of_multiplying<T1, T2>::type, \
#define STRONK_MULTIPLY_TYPE(T1, T2) \
NewUnitType<typename twig::underlying_multiply_operation<T1, T2>::res_type, \
typename twig::unit_lists_of_multiplying<T1, T2>::unit_description_t>

// Macro for getting the full type of dividing two units
// Only works when T1 and T2 has one unit on the multiplication part
// NOLINTNEXTLINE
#define STRONK_DIVIDE_TYPE(T1, T2) \
NewUnitType<typename twig::underlying_type_of_dividing<T1, T2>::type, \
#define STRONK_DIVIDE_TYPE(T1, T2) \
NewUnitType<typename twig::underlying_divide_operation<T1, T2>::res_type, \
typename twig::unit_lists_of_dividing<T1, T2>::unit_description_t>

// Specialize the struct created when multiplying two types. Allows you to add
Expand All @@ -26,7 +26,7 @@
template<> \
struct STRONK_MULTIPLY_TYPE(T1, T2) \
: stronk<STRONK_MULTIPLY_TYPE(T1, T2), \
typename twig::underlying_type_of_multiplying<T1, T2>::type, \
typename twig::underlying_multiply_operation<T1, T2>::res_type, \
unit_type_list_skill_builder<twig::unit_lists_of_multiplying<T1, T2>::unit_description_t>:: \
template skill __VA_OPT__(, ) __VA_ARGS__> \
\
Expand All @@ -44,7 +44,7 @@
template<> \
struct STRONK_DIVIDE_TYPE(T1, T2) \
: stronk<STRONK_DIVIDE_TYPE(T1, T2), \
typename twig::underlying_type_of_dividing<T1, T2>::type, \
typename twig::underlying_divide_operation<T1, T2>::res_type, \
unit_type_list_skill_builder<twig::unit_lists_of_dividing<T1, T2>::unit_description_t>:: \
template skill __VA_OPT__(, ) __VA_ARGS__> \
\
Expand Down
4 changes: 2 additions & 2 deletions include/stronk/stronk.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,13 +135,13 @@ struct can_multiply
{
using can_multiply_with_self = std::true_type;

friend auto operator*=(StronkT& lhs, const StronkT& rhs) noexcept -> StronkT
constexpr friend auto operator*=(StronkT& lhs, const StronkT& rhs) noexcept -> StronkT
{
lhs.template unwrap<StronkT>() *= rhs.template unwrap<StronkT>();
return lhs;
}

friend auto operator*(const StronkT& lhs, const StronkT& rhs) noexcept -> StronkT
constexpr friend auto operator*(const StronkT& lhs, const StronkT& rhs) noexcept -> StronkT
{
return StronkT {lhs.template unwrap<StronkT>() * rhs.template unwrap<StronkT>()};
}
Expand Down
38 changes: 26 additions & 12 deletions include/stronk/unit.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,18 +102,34 @@ struct NewUnitType
unit_type_list_skill_builder<UnitTypeListsT>::template skill>::stronk;
};

// You can specialize this struct if you want another type
// You can specialize this struct if you want another underlying multiply operation
template<stronk_like T1, stronk_like T2>
struct underlying_type_of_multiplying
struct underlying_multiply_operation
{
using type = decltype(std::declval<typename T1::underlying_type>() * std::declval<typename T2::underlying_type>());
using res_type =
decltype(std::declval<typename T1::underlying_type>() * std::declval<typename T2::underlying_type>());

STRONK_FORCEINLINE
constexpr static auto multiply(const typename T1::underlying_type& v1,
const typename T2::underlying_type& v2) noexcept -> res_type
{
return v1 * v2;
}
};

// You can specialize this struct if you want another type
// You can specialize this struct if you want another underlying divide operation
template<stronk_like T1, stronk_like T2>
struct underlying_type_of_dividing
struct underlying_divide_operation
{
using type = decltype(std::declval<typename T1::underlying_type>() / std::declval<typename T2::underlying_type>());
using res_type =
decltype(std::declval<typename T1::underlying_type>() / std::declval<typename T2::underlying_type>());

STRONK_FORCEINLINE
constexpr static auto divide(const typename T1::underlying_type& v1,
const typename T2::underlying_type& v2) noexcept -> res_type
{
return v1 / v2;
}
};

// ==================
Expand All @@ -133,7 +149,7 @@ struct unit_lists_of_multiplying
template<unit_like A, unit_like B>
STRONK_FORCEINLINE constexpr auto operator*(const A& a, const B& b) noexcept
{
auto res = a.template unwrap<A>() * b.template unwrap<B>();
auto res = underlying_multiply_operation<A, B>::multiply(a.template unwrap<A>(), b.template unwrap<B>());

using unit_description_t = typename unit_lists_of_multiplying<A, B>::unit_description_t;
if constexpr (unit_description_t::is_unitless) {
Expand All @@ -142,8 +158,7 @@ STRONK_FORCEINLINE constexpr auto operator*(const A& a, const B& b) noexcept
using pure_t = typename unit_description_t::pure_t; // unwrap out into original type.
return pure_t {static_cast<typename pure_t::underlying_type>(res)};
} else {
using underlying_type = typename underlying_type_of_multiplying<A, B>::type;
return NewUnitType<underlying_type, unit_description_t> {static_cast<underlying_type>(res)};
return NewUnitType<typename underlying_multiply_operation<A, B>::res_type, unit_description_t> {res};
}
}

Expand Down Expand Up @@ -214,7 +229,7 @@ struct unit_lists_of_dividing
template<unit_like A, unit_like B>
STRONK_FORCEINLINE constexpr auto operator/(const A& a, const B& b) noexcept
{
auto res = a.template unwrap<A>() / b.template unwrap<B>();
auto res = underlying_divide_operation<A, B>::divide(a.template unwrap<A>(), b.template unwrap<B>());

using unit_description_t = typename unit_lists_of_dividing<A, B>::unit_description_t;
if constexpr (unit_description_t::is_unitless) {
Expand All @@ -223,8 +238,7 @@ STRONK_FORCEINLINE constexpr auto operator/(const A& a, const B& b) noexcept
using pure_t = typename unit_description_t::pure_t;
return pure_t {static_cast<typename pure_t::underlying_type>(res)};
} else {
using underlying_type = typename underlying_type_of_dividing<A, B>::type;
return NewUnitType<underlying_type, unit_description_t> {static_cast<underlying_type>(res)};
return NewUnitType<typename underlying_divide_operation<A, B>::res_type, unit_description_t> {res};
}
}

Expand Down
51 changes: 51 additions & 0 deletions tests/src/specializers_tests.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#include <gtest/gtest.h>
#include <stronk/specializers.h>
#include <stronk/unit.h>

namespace twig::tests
{
Expand All @@ -16,6 +18,32 @@ struct specializer_type_b : stronk_default_unit<specializer_type_b, int32_t>

namespace twig
{
template<>
struct underlying_multiply_operation<tests::specializer_type_a, tests::specializer_type_b>
{
using res_type = int64_t;

STRONK_FORCEINLINE
constexpr static auto multiply(const typename tests::specializer_type_a::underlying_type& v1,
const typename tests::specializer_type_b::underlying_type& v2) noexcept -> res_type
{
return static_cast<int64_t>(v1) * static_cast<int64_t>(v2) + 1LL;
}
};

template<>
struct underlying_divide_operation<tests::specializer_type_a, tests::specializer_type_b>
{
using res_type = int64_t;

STRONK_FORCEINLINE
constexpr static auto divide(const typename tests::specializer_type_a::underlying_type& v1,
const typename tests::specializer_type_b::underlying_type& v2) noexcept -> res_type
{
return static_cast<int64_t>(v1) / static_cast<int64_t>(v2) + 1LL;
}
};

STRONK_SPECIALIZE_MULTIPLY(tests::specializer_type_a, tests::specializer_type_a);
STRONK_SPECIALIZE_MULTIPLY(tests::specializer_type_a, tests::specializer_type_b, can_equate);
STRONK_SPECIALIZE_DIVIDE(tests::specializer_type_a, tests::specializer_type_b, can_equate, can_stream);
Expand Down Expand Up @@ -46,3 +74,26 @@ STRONK_SPECIALIZE_DIVIDE(tests::specializer_type_b, specializer_type_a_squared);
STRONK_SPECIALIZE_DIVIDE(tests::specializer_type_b, specializer_type_a_divided_by_b); // b^2

} // namespace twig

namespace twig::tests
{

TEST(underlying_multiply_operation, the_multiplying_function_is_overloaded) // NOLINT
{
using res_type = decltype(tests::specializer_type_a {} * tests::specializer_type_b {});

// we have specialized it to return int64_t and add one to the result
static_assert(std::is_same_v<res_type::underlying_type, int64_t>);
EXPECT_EQ(tests::specializer_type_a {10} * tests::specializer_type_b {20}, res_type {200 + 1});
}

TEST(underlying_divide_operation, the_divide_function_is_overloaded) // NOLINT
{
using res_type = decltype(tests::specializer_type_a {} / tests::specializer_type_b {});

// we have specialized it to return int64_t and add one to the result
static_assert(std::is_same_v<res_type::underlying_type, int64_t>);
EXPECT_EQ(tests::specializer_type_a {120} / tests::specializer_type_b {2}, res_type {60 + 1});
}

} // namespace twig::tests
Loading

0 comments on commit f298bca

Please sign in to comment.