-
-
Notifications
You must be signed in to change notification settings - Fork 720
Description
Description
I think it's possible to build ITK with a different C++ standard version than the user application. So for example, someone might build ITK with C++20 and the user application with C++23, or vise versa. Do we actually support such a use case?
If we do indeed support mixing C++ standard versions like that, I'm afraid that the current definition of itk::Math::Absolute(T x) violates the One Definition Rule (ODR):
ITK/Modules/Core/Common/include/itkMath.h
Lines 878 to 945 in 4e88c8e
| template <typename T> | |
| constexpr auto | |
| Absolute(T x) noexcept | |
| { | |
| if constexpr (std::is_same_v<T, bool>) | |
| { | |
| // std::abs does not provide abs for bool, but ITK depends on bool support for abs. | |
| return x; | |
| } | |
| else if constexpr (std::is_integral_v<T>) | |
| { | |
| if constexpr (std::is_signed_v<T>) | |
| { | |
| // Using promotion to std::int32_t instead of std::make_unsigned_t<T> | |
| // to avoid potential overflow when T is a signed 8bit minimum (value -128) | |
| // or signed 15 bit minimum (value -32768). | |
| // Note that -(-128) is still -128 for an 8-bit signed char. | |
| if constexpr (sizeof(T) <= sizeof(int)) | |
| { | |
| #if __cplusplus >= 202302L | |
| return static_cast<std::make_unsigned_t<T>>(std::abs(static_cast<int>(x))); | |
| #else | |
| return static_cast<std::make_unsigned_t<T>>((x < T(0)) ? -x : x); | |
| #endif | |
| } | |
| else if constexpr (sizeof(T) <= sizeof(long int)) | |
| { | |
| #if __cplusplus >= 202302L | |
| return static_cast<std::make_unsigned_t<T>>(std::abs(static_cast<long int>(x))); | |
| #else | |
| return static_cast<std::make_unsigned_t<T>>((x < T(0)) ? -x : x); | |
| #endif | |
| } | |
| else if constexpr (sizeof(T) == sizeof(long long int)) | |
| { | |
| // NOTE: overflow is not resolved if LONG_LONG_INT_MIN value is converted | |
| #if __cplusplus >= 202302L | |
| return static_cast<std::make_unsigned_t<T>>(std::abs(static_cast<long long int>(x))); | |
| #else | |
| return static_cast<std::make_unsigned_t<T>>((x < T(0)) ? -x : x); | |
| #endif | |
| } | |
| } | |
| else | |
| { // In C++17, the std::abs() integer overloads are only for : int, long, and long long. | |
| // unsigned type values std::abs() are supported through implicit type conversions to | |
| // the next larger sized integer type. The 'unsigned long long' failed due to an | |
| // lack of larger sized signed integer type to be promoted to. | |
| // type of x | default std::abs() called | |
| // uint8_t | std::abs( (int32_t) x) | |
| // uint16_t | std::abs( (int32_t) x) | |
| // uint32_t | std::abs( (int64_t) x) | |
| // uint64_t | // compiler error | |
| // This explicit override provides direct support for all unsigned integer types with type promotion. | |
| return x; | |
| } | |
| } | |
| else if constexpr (std::is_floating_point_v<T>) // floating point or complex<> types | |
| { // floating point std::abs is constexpr in c++23 and later | |
| #if __cplusplus >= 202302L | |
| return std::abs(x); | |
| #else | |
| // Note: +0.0 and -0.0 are considered equal, according to ExactlyEquals. They are both considered equal to T{}. | |
| // And they both have the same absolute value: +0.0. | |
| return ExactlyEquals(x, T{}) ? T{} : (x < 0) ? -x : x; | |
| #endif | |
| } | |
| } |
The function definition is different for C++23, that is for __cplusplus >= 202302. Because the function is defined in a header file, its definition within the context of the user application may be different from its definition within ITK, when different C++ standard versions are used.
Versions
This possible issue was introduced by pull request #5797 commit 3cc3f4a (merged on Feb 14, 2026), so it is not yet in a tagged or a released version of ITK
Possible solution
If this is indeed an issue, we might simply remove the C++23 specific code.