Skip to content

Possible ODR violation from itk::Math::Absolute(T x) when mixing C++ standard versions? #5823

@N-Dekker

Description

@N-Dekker

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):

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    type:BugInconsistencies or issues which will cause an incorrect result under some or all circumstances

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions