Skip to content

Commit

Permalink
feature/more documentation fixes (#18)
Browse files Browse the repository at this point in the history
* Updated readme

* Added size, iterate and index skills

* Fix hacking document

* Fix segfault in test

* Gotta get that 100% coverage

* Fix tests to actually call code

* Better credit

* correct
  • Loading branch information
anders-wind authored Aug 30, 2022
1 parent 5a811e0 commit af07b64
Show file tree
Hide file tree
Showing 7 changed files with 280 additions and 44 deletions.
11 changes: 6 additions & 5 deletions HACKING.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ the project:

```json
{
"version": 2,
"version": 3,
"cmakeMinimumRequired": {
"major": 3,
"minor": 14,
"minor": 20,
"patch": 0
},
"configurePresets": [
Expand All @@ -44,6 +44,9 @@ the project:
"inherits": ["dev-mode", "vcpkg", "ci-<os>", "<compiler>"],
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
},
"environment": {
"VCPKG_ROOT": "<local_path_to_vcpkg_exe>"
}
}
],
Expand Down Expand Up @@ -77,9 +80,7 @@ in the terminal.

### Dependency manager

The above preset will make use of the [vcpkg][vcpkg] dependency manager. After
installing it, make sure the `VCPKG_ROOT` environment variable is pointing at
the directory where the vcpkg executable is. On Windows, you might also want
The above preset will make use of the [vcpkg][vcpkg] dependency manager. On Windows, you might also want
to inherit from the `vcpkg-win64-static` preset, which will make vcpkg install
the dependencies as static libraries. This is only necessary if you don't want
to setup `PATH` to run tests.
Expand Down
63 changes: 35 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
[![codecov](https://codecov.io/gh/twig-energy/stronk/branch/main/graph/badge.svg?token=TWO57YT2YA)](https://codecov.io/gh/twig-energy/stronk)
[![license](https://img.shields.io/github/license/twig-energy/stronk)](LICENSE)
```text
==================================================================
==================================================================
* // ) ) /__ ___/ // ) ) // ) ) /| / / // / / *
* (( / / //___/ / // / / //| / / //__ / / *
* \\ / / / ___ ( // / / // | / / //__ / *
* ) ) / / // | | // / / // | / / // \ \ *
* ((___ / / / / // | | ((___/ / // |/ / // \ \ *
* // ) ) /__ ___/ // ) ) // ) ) /| / / // / / *
* (( / / //___/ / // / / //| / / //__ / / *
* \\ / / / ___ ( // / / // | / / //__ / *
* ) ) / / // | | // / / // | / / // \ \ *
* ((___ / / / / // | | ((___/ / // |/ / // \ \ *
==================================================================
==================================================================
```

## An easy to customize, strong type library with built-in support for unit-like behavior
Expand All @@ -20,7 +20,7 @@

#### Why:
- 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 implementations.
- 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.

#### How:
Expand Down Expand Up @@ -73,7 +73,7 @@ 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 scalars) does not change the type.
// 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.
Expand All @@ -97,14 +97,14 @@ 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.5} * Watt {25.0};
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.0};
Watt watt = watt_hours / Hours {3.5};
Hours hours = watt_hours / Watt {25.};
Watt watt = watt_hours / Hours {3.};
}
```

Expand All @@ -119,21 +119,23 @@ struct Euro : twig::stronk<Euro, double, twig::unit>

void introducing_another_type()
{
// There are make functions for std::ratio like types. It simply scales the input value and does not change the type
WattHours watt_hours = twig::make<std::mega, WattHours>(1.);
// 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_watt_hour = Euro {300.} / watt_hours;
auto euros_per_mega_watt_hour = Euro {300.} / one_mega_watt_hour;

// This flexibility allows us to very expessively write code while having the type system checking our code.
Euro price_for_buying_5_megawatt = euros_per_watt_hour * twig::make<std::mega, WattHours>(5.);
// 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 watt_hours_per_euro = 1. / euros_per_watt_hour; // `(Watt * Hours) / Euro`
WattHours watt_hours_affordable_for_500_euros = watt_hours_per_euro * Euro {500.};
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](https://github.com/foonathan)'s [blogpost](https://www.foonathan.net/2016/10/strong-typedefs/) and [Jonathan Boccara](https://github.com/joboccara)'s [blogpost](https://www.fluentcpp.com/2016/12/08/strong-types-for-strong-interfaces/) - both of which have been great sources of inspiration.

## Current list of skills
Skills adds functionality to your stronk types. We have implemented a number of generic skills which should help you get started.

Expand All @@ -143,18 +145,23 @@ Skills adds functionality to your stronk types. We have implemented a number of
- `can_subtract`: binary `operator-` and `operator=-`
- `can_multiply`: binary `operator*` and `operator=*` (not compatible with units, we encourage you to use units instead)
- `can_divide`: binary `operator/` and `operator=/` (not compatible with units, we encourage you to use units instead)
- `can_abs`: `twig::abs` will accept strong-types with this skill. `twig::abs` will not accept strong types without this skill.
- `can_isnan`: `twig::isnan` will accept strong-types with this skill
- `can_stream`: `operator<<(std::ostream)`, stream the underlying value to the stream.
- `can_order`: `operator<=>`, note you probably also want to add `can_equate` or one of its relatives, since the compiler cannot generate equality with the `operator<=>` for stronk types (since its not default implemented)
- `can_abs`: overloads `twig::abs`
- `can_isnan`: overloads `twig::isnan`
- `can_stream`: overloads `operator<<(std::ostream)`, stream the underlying value to the stream.
- `can_order`: `operator<=>`, note you probably also want to add `can_equate`, since the compiler cannot generate equality with the `operator<=>` for stronk types.
- `can_equate`: `operator==` with regular equality
- `can_equate_with_is_close`: `operator==` but with numpy's `is_close` definition of equal
- `can_equate_with_is_close_nan_equals`: `operator==` but with numpy's `is_close` definition of equal, nans being equal
- `can_equate_with_is_close_abs_tol_only`: `operator==` with a small absolute tolerance for difference
- `can_less_than_greater_than`: `operator<` and `operator>` (prefer the `can_order` skill instead)
- `can_less_than_greater_than_or_equal`: operator <= and operator >= (prefer the `can_order` skill instead)
- `can_be_used_as_flag`: for boolean values used as flags
- `can_hash`: implements the `std::hash<T>`.
- `can_hash`: specializes `std::hash<T>`.
- `can_size`: implements `.size()` and `.empty()`
- `can_const_iterate` implements `begin() const`, `end() const`, `cbegin() const` and `cend() const`.
- `can_iterate` adds the `can_const_iterate` as well implementing `begin()`, `end()`.
- `can_const_index` implements `operator[](const auto&) const` and `at(const auto&) const`
- `can_index` adds the `can_const_index` as well implementing `operator[](const auto&)` and `at(const auto&)`.

### Units
- `unit`: enables unit behavior for multiplication and division.
Expand All @@ -165,14 +172,14 @@ Skills adds functionality to your stronk types. We have implemented a number of
- `can_gtest_print`: for printing the values in gtest check macros
- `can_fmt_format`: implements `struct fmt::formatter<T>` with default formatting string `"{}"`. In the future we will add a `can_format` for `std::format`.
- `can_fmt_format_builder<"fmt format string{}">::skill`: implements `struct fmt::formatter<T>`. In the future we will add a `can_format_builder<"std format string">` for `std::format`.
### Prefabs: (see `stronk/prefabs.h`)
- `stronk_default_unit`: arithmetic unit with most of the regular operations
- `stronk_flag`: flag like boolean with equal operators etc.

Adding new skills is easy so feel free to add more.

## Prefabs: (see `stronk/prefabs.h`)
Often you might just need a group of skills for your specific types. For this you can use prefabs.

Credit to https://www.foonathan.net/2016/10/strong-typedefs/ for a great amount of inspiration
- `stronk_default_unit`: arithmetic unit with most of the regular operations
- `stronk_flag`: flag like boolean with equal operators etc.

## Examples:

Expand Down
1 change: 1 addition & 0 deletions examples/specializers_example.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,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");
22 changes: 11 additions & 11 deletions examples/unit_energy_example.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ 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 scalars) does not change the type.
// 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.
Expand All @@ -34,14 +34,14 @@ 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.5} * Watt {25.0};
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.0};
Watt watt = watt_hours / Hours {3.5};
Hours hours = watt_hours / Watt {25.};
Watt watt = watt_hours / Hours {3.};
}

// Lets introduce a type for euros, and start combining more types.
Expand All @@ -52,16 +52,16 @@ struct Euro : twig::stronk<Euro, double, twig::unit>

void introducing_another_type()
{
// There are make functions for std::ratio like types. It simply scales the input value and does not change the type
WattHours watt_hours = twig::make<std::mega, WattHours>(1.);
// 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_watt_hour = Euro {300.} / watt_hours;
auto euros_per_mega_watt_hour = Euro {300.} / one_mega_watt_hour;

// This flexibility allows us to very expessively write code while having the type system checking our code.
Euro price_for_buying_5_megawatt = euros_per_watt_hour * twig::make<std::mega, WattHours>(5.);
// 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 watt_hours_per_euro = 1. / euros_per_watt_hour; // `(Watt * Hours) / Euro`
WattHours watt_hours_affordable_for_500_euros = watt_hours_per_euro * Euro {500.};
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.};
}

auto main() -> int
Expand Down
104 changes: 104 additions & 0 deletions include/stronk/stronk.h
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,110 @@ struct can_hash
template<typename StronkT>
concept can_hash_like = std::same_as<typename StronkT::can_hash_indicator, std::true_type>;

template<typename StronkT>
struct can_size
{
[[nodiscard]] constexpr auto size() const noexcept -> std::size_t
{
return static_cast<const StronkT&>(*this).template unwrap<StronkT>().size();
}
[[nodiscard]] constexpr auto empty() const noexcept -> bool
{
return this->size() == static_cast<std::size_t>(0);
}
};

template<typename StronkT>
struct can_const_iterate
{
[[nodiscard]] constexpr auto begin() const noexcept
{
return static_cast<const StronkT&>(*this).template unwrap<StronkT>().begin();
}
[[nodiscard]] constexpr auto end() const noexcept
{
return static_cast<const StronkT&>(*this).template unwrap<StronkT>().end();
}

[[nodiscard]] constexpr auto cbegin() const noexcept
{
return static_cast<const StronkT&>(*this).template unwrap<StronkT>().begin();
}
[[nodiscard]] constexpr auto cend() const noexcept
{
return static_cast<const StronkT&>(*this).template unwrap<StronkT>().end();
}
};

template<typename StronkT>
struct can_iterate : can_const_iterate<StronkT>
{
[[nodiscard]] constexpr auto begin() const noexcept
{
return static_cast<const StronkT&>(*this).template unwrap<StronkT>().begin();
}
[[nodiscard]] constexpr auto end() const noexcept
{
return static_cast<const StronkT&>(*this).template unwrap<StronkT>().end();
}

[[nodiscard]] constexpr auto cbegin() const noexcept
{
return static_cast<const StronkT&>(*this).template unwrap<StronkT>().begin();
}
[[nodiscard]] constexpr auto cend() const noexcept
{
return static_cast<const StronkT&>(*this).template unwrap<StronkT>().end();
}

[[nodiscard]] constexpr auto begin() noexcept
{
return static_cast<StronkT&>(*this).template unwrap<StronkT>().begin();
}
[[nodiscard]] constexpr auto end() noexcept
{
return static_cast<StronkT&>(*this).template unwrap<StronkT>().end();
}
};

template<typename StronkT>
struct can_const_index
{
[[nodiscard]] constexpr auto operator[](const auto& indexer) const noexcept -> const auto&
{
return static_cast<const StronkT&>(*this).template unwrap<StronkT>()[indexer];
}

[[nodiscard]] constexpr auto at(const auto& indexer) const -> const auto&
{
return static_cast<const StronkT&>(*this).template unwrap<StronkT>().at(indexer);
}
};

template<typename StronkT>
struct can_index
{
[[nodiscard]] constexpr auto operator[](const auto& indexer) const noexcept -> const auto&
{
return static_cast<const StronkT&>(*this).template unwrap<StronkT>()[indexer];
}

[[nodiscard]] constexpr auto at(const auto& indexer) const -> const auto&
{
return static_cast<const StronkT&>(*this).template unwrap<StronkT>().at(indexer);
}

[[nodiscard]] constexpr auto operator[](const auto& indexer) noexcept -> auto&
{
return static_cast<StronkT&>(*this).template unwrap<StronkT>()[indexer];
}

[[nodiscard]] constexpr auto at(const auto& indexer) -> auto&
{
return static_cast<StronkT&>(*this).template unwrap<StronkT>().at(indexer);
}
};

} // namespace twig

template<twig::can_hash_like T>
Expand Down
14 changes: 14 additions & 0 deletions tests/src/extensions_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,18 @@ TEST(can_absl_hash, can_absl_hash_adds_the_absl_hash_function) // NOLINT
}
}

struct a_can_gtest_print_type : stronk<a_can_gtest_print_type, int, can_gtest_print>
{
using stronk::stronk;
};

TEST(can_gtest_print, streaming_to_ostream_prints_the_value) // NOLINT
{
auto val = a_can_gtest_print_type {5};
auto sstream = std::stringstream();
PrintTo(val, &sstream);

EXPECT_EQ(sstream.str(), "5");
}

} // namespace twig
Loading

0 comments on commit af07b64

Please sign in to comment.