-
-
Notifications
You must be signed in to change notification settings - Fork 886
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix unexpected type casting issues #2139
Conversation
Solves unexpected behaviour between different architechtures where the sizes of `size_t`, `maxint_t` etc may differ. NP-343
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The changes make sense imo. But I would rather have a more robust solution in place. Using Concepts to guard against size differences. This is just a quick example put together. Use it as inspiration. Check it out on Compiler Explorer, just uncomment the idx_no_compile
variable to see it in action. https://godbolt.org/z/GsK7Wc569
constexpr everything ;-)
#include <type_traits>
#include <concepts>
#include <cstdint>
template<typename T, typename U>
concept SameOrLargerSize = requires {
// Check for arithmetic types
requires std::is_arithmetic_v<T> && std::is_arithmetic_v<U>;
// Ensure the size of U is at least the same as T
requires sizeof(T) >= sizeof(U);
// Additional checks for integral and floating-point types
// To handle signed/unsigned comparisons
requires (!std::is_integral_v<T> || !std::is_integral_v<U>) ||
(std::is_signed_v<T> == std::is_signed_v<U>);
// To prevent comparing integral and floating-point types directly
requires (!std::is_integral_v<T> || !std::is_floating_point_v<U>) &&
(!std::is_floating_point_v<T> || !std::is_integral_v<U>);
};
// Helper concept to check for integral types that are the same size or larger
template<typename T, typename U>
concept SameOrLargeSizeInteger = SameOrLargerSize<T, U> && std::is_integral_v<T>;
// Helper concept to check for floating-point types that are the same size or larger
template<typename T, typename U>
concept SameOrLargeSizeFloatingPoint = SameOrLargerSize<T, U> && std::is_floating_point_v<T>;
struct LayerIndex
{
using value_type = std::int32_t;
value_type value{};
// Constructor for floating-point types that are the same size or larger
template<typename U>
requires SameOrLargeSizeFloatingPoint<value_type, U>
constexpr explicit LayerIndex(const U val) noexcept
: value{ static_cast<value_type>(val) }
{
}
// Constructor for integral types that are the same size or larger
template<typename U>
requires SameOrLargeSizeInteger<value_type, U>
constexpr LayerIndex(const U val) noexcept
: value{ static_cast<value_type>(val) }
{
}
constexpr LayerIndex operator-(const LayerIndex& other) const noexcept
{
return LayerIndex(value - other.value); // Explicitly construct the result
}
};
int main() {
constexpr std::size_t bigger { 20 };
constexpr std::int8_t smaller { 5 };
constexpr LayerIndex idx(20);
constexpr LayerIndex idx_compile(smaller);
// const LayerIndex idx_no_compile(bigger); // This will still fail due to size mismatch
constexpr auto comp_idx = idx - idx_compile;
return static_cast<int>(comp_idx.value);
}
When un-commenting the idx_no_compile
you would get the following error
@jellespijker in the example I shared above the unexpected behaviour occurs when casting a smaller to a larger type due to the sign flipping. This is a different issue then you're trying solve with the stated example. |
The example was intended as inspiration and a quick showcase. Maybe the example below is more suited and recognizable. #include <type_traits>
#include <concepts>
#include <cstdint>
template<typename T, typename U>
concept SafeConversion = requires {
// Check for arithmetic types
requires std::is_arithmetic_v<T> && std::is_arithmetic_v<U>;
// Ensure the size of T is at least the same as U to avoid truncation
requires sizeof(T) >= sizeof(U);
// Handle signed/unsigned conversions to prevent unintended behavior
requires (!std::is_integral_v<T> || !std::is_integral_v<U>) ||
(std::is_signed_v<T> == std::is_signed_v<U>) ||
(std::is_unsigned_v<T> && std::is_signed_v<U> && sizeof(T) > sizeof(U)); // Allow if unsigned T is larger
// Prevent direct comparisons between integral and floating-point types
requires (!std::is_integral_v<T> || !std::is_floating_point_v<U>) &&
(!std::is_floating_point_v<T> || !std::is_integral_v<U>);
};
int main() {
std::size_t x = 1;
std::int64_t y = -x;
static_assert(SafeConversion<decltype(x), decltype(y)>);
return y;
} Resulting in:
|
@jellespijker You really have a concepts-based solution for every situation 😛 In the present situation, this would indeed generate a proper error instead of a warning, but it doesn't actually fix the issue. What we discussed briefly with @casperlamboo would be to have our own types for everything (which we already do in the engine, partially), and proper conversion operators between them, so that you can have a compact, readable and error/warning-free code. However we agreed for now to just fix this issue locally, and possibly add a C&C ticket to discuss this. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One minor suggestion, otherwise looks good
I've been trying to compile this in WASM, and I believe I found one more place that needs to be cast:
On my architecture this ends up saying layer_index < Really big number. |
Description
We encountered some issues when compiling for a different operating system where
size_t
is 32bit rather then 64bit. this causes the following simple exampleto unexpectedly output
4294967295
rather then-1
.Note: this PR is based on https://github.com/Ultimaker/CuraEngine/tree/NP-327_emscripten_communication; merge PR #2131 before this one.
Type of change
Prevent unexpected integer overflows by more explicitly casting.
How Has This Been Tested?
yes
Test Configuration:
Checklist:
NP-343