Implement simple vector and tensor types#735
Conversation
20a6db0 to
7bc1e18
Compare
mpusz
left a comment
There was a problem hiding this comment.
Hi!
Thanks for contributing! I had time to review only cartesian_vector changes and provided some feedback that I believe will also be meaningful to another type as well.
| #include <mp-units/bits/requires_hosted.h> | ||
| // | ||
| #include <mp-units/bits/module_macros.h> | ||
| #include <mp-units/bits/requires_hosted.h> |
There was a problem hiding this comment.
We put requires_hosted.h as the first header in all HOSTED implementations. Do not use alphabetical order for this header.
There was a problem hiding this comment.
with this order, clang-format is failing
There was a problem hiding this comment.
Because you removed the comment below which was there to separate two header lists.
| namespace mp_units { | ||
|
|
||
| MP_UNITS_EXPORT template<detail::Scalar T> | ||
| MP_UNITS_EXPORT template<detail::Scalar T = double> |
There was a problem hiding this comment.
It is better to provide template defaults in the implementation rather than in one of (possibly many) forward declarations.
| requires requires(T t, U u) { t + u; } | ||
| requires requires(const T& t, const U& u) { t + u; } |
There was a problem hiding this comment.
I've read that adding const doesn't mutate an input which is desired in arithmetic operations (https://en.cppreference.com/w/cpp/language/operator_arithmetic.html)
There was a problem hiding this comment.
You are right: https://godbolt.org/z/3zW8ns6YP. Thanks!
| namespace detail { | ||
|
|
||
| struct cartesian_vector_iface { | ||
| // A + B |
There was a problem hiding this comment.
Such comments do not improve anything.
| // A % B (integral: %, floating: fmod into CT) | ||
| template<typename T, typename U> | ||
| requires requires(T t, U u) { t * u; } | ||
| [[nodiscard]] friend constexpr auto operator*(const cartesian_vector<T>& lhs, const U& rhs) | ||
| requires(requires(const T& t, const U& u) { t % u; }) || (std::floating_point<T> && std::floating_point<U>) | ||
| [[nodiscard]] friend constexpr auto operator%(const cartesian_vector<T>& lhs, const cartesian_vector<U>& rhs) | ||
| { | ||
| using CT = std::common_type_t<T, U>; | ||
| if constexpr (std::floating_point<T> && std::floating_point<U>) { | ||
| using std::fmod; | ||
| return ::mp_units::cartesian_vector<CT>{static_cast<CT>(fmod(static_cast<long double>(lhs._coordinates_[0]), | ||
| static_cast<long double>(rhs._coordinates_[0]))), | ||
| static_cast<CT>(fmod(static_cast<long double>(lhs._coordinates_[1]), | ||
| static_cast<long double>(rhs._coordinates_[1]))), | ||
| static_cast<CT>(fmod(static_cast<long double>(lhs._coordinates_[2]), | ||
| static_cast<long double>(rhs._coordinates_[2])))}; | ||
| } else { | ||
| return ::mp_units::cartesian_vector<CT>{static_cast<CT>(lhs._coordinates_[0] % rhs._coordinates_[0]), | ||
| static_cast<CT>(lhs._coordinates_[1] % rhs._coordinates_[1]), | ||
| static_cast<CT>(lhs._coordinates_[2] % rhs._coordinates_[2])}; | ||
| } | ||
| } |
There was a problem hiding this comment.
There is no op% for floating-point types for a reason. We should behave like fundamental types, so it should not be available as well for floating-point representation types. If we care about modulo for such types, we should provide fmod non-member function. See fmod from math.h for more info.
| template<typename FormatContext> | ||
| auto format(const mp_units::cartesian_vector<T>& vec, FormatContext& ctx) const | ||
| template<typename Ctx> | ||
| auto format(const mp_units::cartesian_vector<T>& v, Ctx& ctx) const | ||
| { | ||
| return format_to(ctx.out(), "[{}, {}, {}]", vec[0], vec[1], vec[2]); | ||
| return format_to(ctx.out(), "[{}, {}, {}]", v[0], v[1], v[2]); |
There was a problem hiding this comment.
Please restore the previous code. FormatContext provides more info and is used consistently in the entire project for all formatters.
v is too short as an identifier and often causes some stupid shadowing errors in MSVC.
| import mp_units; | ||
| #else | ||
| #include <mp-units/cartesian_vector.h> | ||
| #if MP_UNITS_HOSTED |
There was a problem hiding this comment.
Runtime tests are only run for HOSTED so no need to check here.
| REQUIRE(v[0] == 0); | ||
| REQUIRE(v[1] == 0); | ||
| REQUIRE(v[2] == 0); |
There was a problem hiding this comment.
This is why I didn't provide default construction.
|
|
||
| SECTION("zero arguments") | ||
| { | ||
| cartesian_vector<double> v{}; |
There was a problem hiding this comment.
This is how zeros should be set, but this collides with your default-constructor now
| using namespace Catch::Matchers; | ||
| using Catch::Matchers::WithinAbs; | ||
|
|
||
| TEST_CASE("cartesian_vector initialization and access", "[vector]") |
There was a problem hiding this comment.
I personally prefer to have fewer TEST_CASES in catch, so it is easier to manage. For example, if I want to check if all vector tests still work, it can check the status of one easy-to-find test rather than searching for many different strings.
Filtering by a tag is not often an option.
|
Hi! Thank you so much! I'll start reading and implementing changes - thank you and sorry in advance for any problems! :) |
| cartesian_vector() = default; | ||
| cartesian_vector(const cartesian_vector&) = default; | ||
| cartesian_vector(cartesian_vector&&) = default; | ||
| cartesian_vector& operator=(const cartesian_vector&) = default; | ||
| cartesian_vector& operator=(cartesian_vector&&) = default; | ||
|
|
||
| template<typename... Args> | ||
| requires(... && std::constructible_from<T, Args>) | ||
| requires(sizeof...(Args) <= 3) && (... && std::constructible_from<T, Args>) | ||
| constexpr explicit(!(... && std::convertible_to<Args, T>)) cartesian_vector(Args&&... args) : | ||
| _coordinates_{static_cast<T>(std::forward<Args>(args))...} | ||
| { | ||
| } | ||
|
|
||
| template<typename U> | ||
| requires std::constructible_from<T, U> | ||
| requires std::constructible_from<T, const U&> | ||
| constexpr explicit(!std::convertible_to<U, T>) cartesian_vector(const cartesian_vector<U>& other) : | ||
| _coordinates_{static_cast<T>(other[0]), static_cast<T>(other[1]), static_cast<T>(other[2])} | ||
| { | ||
| } | ||
|
|
||
| template<typename U> | ||
| requires std::constructible_from<T, U> | ||
| requires std::constructible_from<T, U&&> | ||
| constexpr explicit(!std::convertible_to<U, T>) cartesian_vector(cartesian_vector<U>&& other) : | ||
| _coordinates_{static_cast<T>(std::move(other[0])), static_cast<T>(std::move(other[1])), | ||
| static_cast<T>(std::move(other[2]))} |
There was a problem hiding this comment.
Let's remove all of those. This will result in an aggregate type (such as std::array), which should solve all our issues. Please, change the comment next to a public member to reflect that.
f3f2391 to
00b2e74
Compare
|
@mpusz could you help me fix the pipeline? I tried in some many ways and I have no idea how to fix it :c |
|
I am currently on vacation in Hawaii, so I don't have much time to look into it. I will check it out when I am back. |
1618ca3 to
e564751
Compare
e564751 to
59f9298
Compare
3e231f3 to
1ecb38f
Compare
| #include <mp-units/bits/requires_hosted.h> | ||
| // | ||
| #include <mp-units/bits/module_macros.h> | ||
| #include <mp-units/bits/requires_hosted.h> |
| MP_UNITS_EXPORT template<typename T, typename U> | ||
| requires requires(const T& t, const U& u) { t + u; } | ||
| [[nodiscard]] constexpr auto operator+(const cartesian_vector<T>& lhs, const cartesian_vector<U>& rhs) | ||
| { | ||
| return ::mp_units::cartesian_vector{lhs._coordinates_[0] + rhs._coordinates_[0], | ||
| lhs._coordinates_[1] + rhs._coordinates_[1], | ||
| lhs._coordinates_[2] + rhs._coordinates_[2]}; | ||
| } |
There was a problem hiding this comment.
All of the operators and other customization points should be hidden friends as they were before.
| { | ||
| cartesian_vector<double> v2 = v1; | ||
| cartesian_vector v1{1.0, 2.0, 3.0}; | ||
| cartesian_vector<double> v2 = v1; // Use initialization instead of assignment |
test/static/quantity_test.cpp
Outdated
| static_assert(std::constructible_from<cartesian_vector<double>, quantity<one, cartesian_vector<double>>>); | ||
| static_assert(!std::convertible_to<quantity<one, double>, cartesian_vector<double>>); | ||
| static_assert(std::constructible_from<cartesian_vector<double>, quantity<one, double>>); | ||
| static_assert(std::is_aggregate_v<cartesian_vector<double>>); |
There was a problem hiding this comment.
This change makes no sense and removes proper test
| #else | ||
| #include <mp-units/cartesian_vector.h> | ||
| #include <mp-units/ext/format.h> | ||
| #include <sstream> |
| #endif | ||
|
|
||
| #ifndef MP_UNITS_MODULES | ||
| #include <sstream> |
| REQUIRE_THAT(u.magnitude(), WithinAbs(1.0, 1e-12)); | ||
| REQUIRE_THAT(u[0], WithinAbs(3.0 / 5.0, 1e-12)); | ||
| REQUIRE_THAT(u[1], WithinAbs(4.0 / 5.0, 1e-12)); | ||
| REQUIRE_THAT(u[2], WithinAbs(0.0, 1e-12)); |
| } | ||
| } | ||
|
|
||
| TEST_CASE("cartesian_vector text output", "[vector][fmt][ostream]") |
There was a problem hiding this comment.
Why did you remove my tests that were testing each text output separately for different representation types?
3d9fce9 to
69567bc
Compare
61635aa to
8cfa4c8
Compare
| #include <mp-units/bits/requires_hosted.h> | ||
| // | ||
| #include <mp-units/bits/module_macros.h> | ||
| #include <mp-units/bits/requires_hosted.h> |
There was a problem hiding this comment.
Because you removed the comment below which was there to separate two header lists.
f69ccc8 to
edcaf11
Compare
edcaf11 to
bfde546
Compare
9300ec9 to
e82ac7d
Compare
|
In this discussion you mentioned to delete constructors to create an aggregate type. But after that I believe it is impossible to use Should I add this or delete/modify this test? Or I misunderstood something? |
It is hard for me to judge without seeing the actual issue. Making this class an aggregate should not prevent this test from working. Unless I forgot about something now. |
| #include <mp-units/bits/module_macros.h> | ||
| #include <mp-units/framework/customization_points.h> | ||
| #include <mp-units/framework/representation_concepts.h> | ||
| #include <type_traits> |
There was a problem hiding this comment.
I believe this is not needed (included below). Also, this is not a correct section to include this file as it will collide with import std; if used.
| template<std::convertible_to<T> U> | ||
| constexpr cartesian_vector& operator=(const cartesian_vector<U>& other) | ||
| { | ||
| _coordinates_[0] = other[0]; | ||
| _coordinates_[1] = other[1]; | ||
| _coordinates_[2] = other[2]; | ||
| return *this; | ||
| } | ||
|
|
||
| template<std::convertible_to<T> U> | ||
| constexpr cartesian_vector& operator=(cartesian_vector<U>&& other) | ||
| { | ||
| _coordinates_[0] = std::move(other[0]); | ||
| _coordinates_[1] = std::move(other[1]); | ||
| _coordinates_[2] = std::move(other[2]); | ||
| return *this; | ||
| } |
| return {-_coordinates_[0], -_coordinates_[1], -_coordinates_[2]}; | ||
| return cartesian_vector{-_coordinates_[0], -_coordinates_[1], -_coordinates_[2]}; |
| template<typename U> | ||
| requires requires(const T& t, const U& u) { t + u; } | ||
| [[nodiscard]] friend constexpr auto operator+(const cartesian_vector& lhs, const cartesian_vector<U>& rhs) | ||
| { | ||
| return ::mp_units::cartesian_vector{lhs._coordinates_[0] + rhs._coordinates_[0], | ||
| lhs._coordinates_[1] + rhs._coordinates_[1], | ||
| lhs._coordinates_[2] + rhs._coordinates_[2]}; | ||
| } | ||
|
|
||
| template<typename U> | ||
| requires requires(const T& t, const U& u) { t - u; } | ||
| [[nodiscard]] friend constexpr auto operator-(const cartesian_vector& lhs, const cartesian_vector<U>& rhs) | ||
| { | ||
| return ::mp_units::cartesian_vector{lhs._coordinates_[0] - rhs._coordinates_[0], | ||
| lhs._coordinates_[1] - rhs._coordinates_[1], | ||
| lhs._coordinates_[2] - rhs._coordinates_[2]}; | ||
| } | ||
|
|
||
| template<typename U> | ||
| requires(!treat_as_floating_point<T> && !treat_as_floating_point<U> && requires(const T& t, const U& u) { t % u; }) | ||
| [[nodiscard]] friend constexpr auto operator%(const cartesian_vector& lhs, const cartesian_vector<U>& rhs) | ||
| { | ||
| using CT = std::common_type_t<T, U>; | ||
| return ::mp_units::cartesian_vector<CT>{static_cast<CT>(lhs._coordinates_[0] % rhs._coordinates_[0]), | ||
| static_cast<CT>(lhs._coordinates_[1] % rhs._coordinates_[1]), | ||
| static_cast<CT>(lhs._coordinates_[2] % rhs._coordinates_[2])}; | ||
| } | ||
|
|
||
| template<typename S> | ||
| requires(!std::same_as<std::remove_cvref_t<S>, cartesian_vector>) && detail::NotQuantity<S> | ||
| [[nodiscard]] friend constexpr auto operator*(const cartesian_vector& vector, const S& scalar) | ||
| { | ||
| return ::mp_units::cartesian_vector{vector._coordinates_[0] * scalar, vector._coordinates_[1] * scalar, | ||
| vector._coordinates_[2] * scalar}; | ||
| } | ||
|
|
||
| template<typename S> | ||
| requires(!std::same_as<std::remove_cvref_t<S>, cartesian_vector>) && detail::NotQuantity<S> | ||
| [[nodiscard]] friend constexpr auto operator*(const S& scalar, const cartesian_vector& vector) | ||
| { | ||
| return vector * scalar; | ||
| } | ||
|
|
||
| template<typename S> | ||
| requires(!std::same_as<std::remove_cvref_t<S>, cartesian_vector>) && detail::NotQuantity<S> | ||
| [[nodiscard]] friend constexpr auto operator/(const cartesian_vector& vector, const S& scalar) | ||
| { | ||
| return ::mp_units::cartesian_vector{vector._coordinates_[0] / scalar, vector._coordinates_[1] / scalar, | ||
| vector._coordinates_[2] / scalar}; | ||
| } | ||
|
|
||
| template<std::equality_comparable_with<T> U> | ||
| [[nodiscard]] friend constexpr bool operator==(const cartesian_vector& lhs, const cartesian_vector<U>& rhs) | ||
| { | ||
| return lhs._coordinates_[0] == rhs._coordinates_[0] && lhs._coordinates_[1] == rhs._coordinates_[1] && | ||
| lhs._coordinates_[2] == rhs._coordinates_[2]; | ||
| } | ||
|
|
||
| template<typename U> | ||
| requires requires(const T& t, const U& u, decltype(t * u) v) { | ||
| t * u; | ||
| v + v; | ||
| } | ||
| [[nodiscard]] friend constexpr auto scalar_product(const cartesian_vector& lhs, const cartesian_vector<U>& rhs) | ||
| { | ||
| return lhs._coordinates_[0] * rhs._coordinates_[0] + lhs._coordinates_[1] * rhs._coordinates_[1] + | ||
| lhs._coordinates_[2] * rhs._coordinates_[2]; | ||
| } | ||
|
|
||
| template<typename U> | ||
| requires requires(const T& t, const U& u, decltype(t * u) v) { | ||
| t * u; | ||
| v - v; | ||
| } | ||
| [[nodiscard]] friend constexpr auto vector_product(const cartesian_vector& lhs, const cartesian_vector<U>& rhs) | ||
| { | ||
| return ::mp_units::cartesian_vector{ | ||
| lhs._coordinates_[1] * rhs._coordinates_[2] - lhs._coordinates_[2] * rhs._coordinates_[1], | ||
| lhs._coordinates_[2] * rhs._coordinates_[0] - lhs._coordinates_[0] * rhs._coordinates_[2], | ||
| lhs._coordinates_[0] * rhs._coordinates_[1] - lhs._coordinates_[1] * rhs._coordinates_[0]}; | ||
| } | ||
|
|
There was a problem hiding this comment.
All of those make the class template operations asymmetrical. RHS argument will not convert, while the LHS will. Please restore my cartesian_vector_iface to fix it -> both arguments will not convert. If you would like to enable conversion for the arguments, it is doable, but requires even more overloads.
| template<class T> | ||
| inline constexpr bool treat_as_floating_point<cartesian_vector<T>> = treat_as_floating_point<T>; | ||
|
|
||
| template<class S, class T> | ||
| inline constexpr bool is_value_preserving<S, cartesian_vector<T>> = is_value_preserving<S, T>; |
There was a problem hiding this comment.
Not needed as this class provides value_type (https://mpusz.github.io/mp-units/latest/how_to_guides/integration/using_custom_representation_types/#value_type-or-element_type).
| namespace std { | ||
| template<class T, class U> | ||
| struct common_type<mp_units::cartesian_vector<T>, mp_units::cartesian_vector<U>> { | ||
| using type = mp_units::cartesian_vector<common_type_t<T, U>>; | ||
| }; | ||
|
|
||
| template<class T, class U> | ||
| struct common_type<mp_units::cartesian_vector<T>, U> { | ||
| using type = mp_units::cartesian_vector<common_type_t<T, U>>; | ||
| }; | ||
|
|
||
| template<class T, class U> | ||
| struct common_type<U, mp_units::cartesian_vector<T>> { | ||
| using type = mp_units::cartesian_vector<common_type_t<U, T>>; | ||
| }; |
There was a problem hiding this comment.
OK, it seems correct, but why did you have to do it? Was it required by some specific test?
| cartesian_vector v1{1.0, 2.0, 3.0}; | ||
| cartesian_vector v2{4.0, 5.0, 6.0}; | ||
| v1 += v2; | ||
| REQUIRE(v1[0] == 5.0); | ||
| REQUIRE(v1[1] == 7.0); | ||
| REQUIRE(v1[2] == 9.0); | ||
| } | ||
|
|
||
| SECTION("cartesian_vector compound assignment subtraction") | ||
| { | ||
| cartesian_vector v1{4.0, 5.0, 6.0}; | ||
| cartesian_vector v2{1.0, 2.0, 3.0}; | ||
| v1 -= v2; | ||
| REQUIRE(v1[0] == 3.0); | ||
| REQUIRE(v1[1] == 3.0); | ||
| REQUIRE(v1[2] == 3.0); | ||
| } | ||
|
|
||
| SECTION("cartesian_vector compound assignment scalar multiplication") | ||
| { | ||
| cartesian_vector v{1.0, 2.0, 3.0}; | ||
| v *= 2.0; | ||
| REQUIRE(v[0] == 2.0); | ||
| REQUIRE(v[1] == 4.0); | ||
| REQUIRE(v[2] == 6.0); | ||
| } | ||
|
|
||
| SECTION("cartesian_vector compound assignment scalar division") | ||
| { | ||
| cartesian_vector v{2.0, 4.0, 6.0}; | ||
| v /= 2.0; | ||
| REQUIRE(v[0] == 1.0); | ||
| REQUIRE(v[1] == 2.0); | ||
| REQUIRE(v[2] == 3.0); | ||
| SECTION("operator+=") | ||
| { | ||
| cartesian_vector v1{1.0, 2.0, 3.0}; | ||
| cartesian_vector v2{4.0, 5.0, 6.0}; | ||
| v1 += v2; | ||
| REQUIRE(v1[0] == 5.0); | ||
| REQUIRE(v1[1] == 7.0); | ||
| REQUIRE(v1[2] == 9.0); | ||
| } | ||
| SECTION("cartesian_vector compound assignment subtraction") | ||
| { | ||
| cartesian_vector v1{4.0, 5.0, 6.0}; | ||
| cartesian_vector v2{1.0, 2.0, 3.0}; | ||
| v1 -= v2; | ||
| REQUIRE(v1[0] == 3.0); | ||
| REQUIRE(v1[1] == 3.0); | ||
| REQUIRE(v1[2] == 3.0); | ||
| } | ||
| SECTION("cartesian_vector compound assignment scalar multiplication") | ||
| { | ||
| cartesian_vector v{1.0, 2.0, 3.0}; | ||
| v *= 2.0; | ||
| REQUIRE(v[0] == 2.0); | ||
| REQUIRE(v[1] == 4.0); | ||
| REQUIRE(v[2] == 6.0); | ||
| } | ||
| SECTION("cartesian_vector compound assignment scalar division") | ||
| { | ||
| cartesian_vector v{2.0, 4.0, 6.0}; | ||
| v /= 2.0; | ||
| REQUIRE(v[0] == 1.0); | ||
| REQUIRE(v[1] == 2.0); | ||
| REQUIRE(v[2] == 3.0); | ||
| } | ||
| } |
There was a problem hiding this comment.
It seems to be formatted incorrectly, making it hard to compare. Please use pre-commit to format your files before commit (https://mpusz.github.io/mp-units/latest/getting_started/contributing/#unified-code-formatting).
| SECTION("cartesian_vector subtraction") | ||
| SECTION("cartesian_vector substraction") |
| static_assert(std::constructible_from<cartesian_vector<double>, quantity<one, cartesian_vector<double>>>); | ||
| static_assert(!std::convertible_to<quantity<one, double>, cartesian_vector<double>>); | ||
| static_assert(std::constructible_from<cartesian_vector<double>, quantity<one, double>>); | ||
|
|
Added
Types
Vector ops (a,b : cartesian_vector ; s : scalar)