Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ repos:
entry: 'Utilities/Hooks/prepare-commit-msg'
language: system
stages: [prepare-commit-msg]
- id: itk-cxx-abi-guard
name: 'Standard-dependent #if must test ITK_CXX_ABI macros'
entry: '^\s*#\s*(?!(ifdef|ifndef)\s+__cplusplus\b|if\s+!?\s*defined\s*\(?\s*__cplusplus\s*\)?\s*(/[/*].*)?$)(if|ifdef|ifndef|elif|elifdef|elifndef)\b[^/]*(__cplusplus\b|_MSVC_LANG|__cpp_)'
language: pygrep
files: '^Modules/.*\.(h|hxx)$'
exclude: '^Modules/ThirdParty/'
- id: check-setup-for-development
name: 'Check version of SetupForDevelopment'
entry: 'Utilities/Hooks/check-setup-for-development'
Expand Down
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ rows labeled **Agent default** bind AI agents only. Agents set
5. **Link errors → check `itk-module.cmake`** — missing `DEPENDS` or `PRIVATE_DEPENDS` is the usual cause.
6. **Licensing** — verify AI output does not reproduce third-party code in conflict with Apache 2.0.
7. **Committed text must be load-bearing** — reviewers reject commit messages, in-source comments, and PR bodies that restate the diff or cite transient URLs, and in-source comments that narrate history ([prose-budget.md](./Documentation/AI/prose-budget.md)). Agents also follow its numeric caps. Cut prose, not code.
8. **Never select code by `__cplusplus` value, `_MSVC_LANG`, or a `__cpp_*` macro in a header** (`#ifdef __cplusplus` is fine) — clients may use a newer C++ standard or another compiler than ITK, which breaks the One Definition Rule; test `ITK_CXX_ABI` or an `ITK_CXX_ABI_STD_*` macro from `itkConfigure.h` ([compiler-cautions.md](./Documentation/AI/compiler-cautions.md) section 16).

## Resources

Expand Down
2 changes: 2 additions & 0 deletions CMake/ITKConfig.cmake.in
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ set(ITK_WRAPPING "@ITK_WRAPPING@")

# expose C++ standard used to build ITK for supporting external packages
set(ITK_CXX_STANDARD "@CMAKE_CXX_STANDARD@")
# Lowest C++ standard a project using this ITK may compile with.
set(ITK_CXX_ABI "@ITK_CXX_ABI@")

if( NOT DEFINED ITK_WRAP_PYTHON)
set(ITK_WRAP_PYTHON "@ITK_WRAP_PYTHON@")
Expand Down
49 changes: 49 additions & 0 deletions Documentation/AI/compiler-cautions.md
Original file line number Diff line number Diff line change
Expand Up @@ -646,4 +646,53 @@ When refactoring existing code, verify each item:
- [ ] ITK deprecated macros replaced (`itkTypeMacro`, `ITK_DISALLOW_COPY_AND_ASSIGN`, `itkStaticConstMacro`)
- [ ] No new clang-tidy diagnostics introduced; `llvmlibc-*`, `readability-identifier-length`, and `google-*` warnings ignored if pre-existing
- [ ] `enum class` with `/** */` doxygen comment has a `\class` tag, or use plain `//` comment instead (KWStyle rule)
- [ ] No `#if` on `__cplusplus`, `_MSVC_LANG`, or a `__cpp_*` macro in a header — test `ITK_CXX_ABI` or an `ITK_CXX_ABI_STD_*` macro (section 16)
- [ ] `NumericTraits<float>::min()` is the smallest *positive* value, not the most negative — special-case floating-point dynamic-range defaults

---

## 16. Standard-Dependent Code Must Test `ITK_CXX_ABI`

**Bug:** ITK supports clients built with a newer C++ standard, and with another
compiler or standard library, than ITK itself. A header that selects code with
`__cplusplus`, `_MSVC_LANG`, or a feature-test macro (`__cpp_concepts`,
`__cpp_lib_endian`) then gives ITK's libraries and the client different
definitions of the same class or inline function. That violates the One
Definition Rule: undefined behavior, no diagnostic.

**Rule:** ITK decides once, at configure time, and records the decisions in
`itkConfigure.h`. Headers under `Modules/` (outside `ThirdParty`) test only
those macros:

- `ITK_CXX_ABI` (17, 20, 23, or 26) for language features. It always equals the
standard ITK is built with and is the floor for every client; do not add an
option that sets it lower.
- `ITK_CXX_ABI_STD_<FEATURE>` (for example `ITK_CXX_ABI_STD_ATOMIC_REF`) for a
standard library feature, which can lag the language level.

```cpp
// BAD - depends on the compiler and standard of whoever includes the header:
#if __cplusplus >= 202002L
#ifdef __cpp_concepts
#if ITK_CXX_ABI >= 20 && defined(__cpp_lib_atomic_ref)

// GOOD - the same answer in ITK and in every client of this build:
#if ITK_CXX_ABI >= 20
#if defined(ITK_CXX_ABI_STD_ATOMIC_REF)
```

- A new library feature needs a `check_cxx_source_compiles` probe in
`Modules/Core/Common/CMakeLists.txt` and a `#cmakedefine` in
`itkConfigure.h.in`.
- Declarations, base classes, and `#include` lines need `#if`.
- `if constexpr (ITK_CXX_ABI >= 20)` is allowed inside a function body only
when both branches compile in every supported standard.
- Do not add a newer-standard form only for nicer diagnostics; it doubles the
code paths CI must build. Write the C++17 form.
- `.cxx` files are compiled once with ITK's own standard and are not affected.
- An existence check for C compatibility (`#ifdef __cplusplus`) is fine.
- The rule also covers `__has_include` and standard library identity macros
(`_LIBCPP_VERSION`, `__GLIBCXX__`) when they select declarations.
- The `itk-cxx-abi-guard` pre-commit hook rejects single-line `__cplusplus`,
`_MSVC_LANG`, and `__cpp_*` conditions only; check backslash continuations
and the forms in the previous item by hand.
34 changes: 34 additions & 0 deletions Documentation/docs/migration_guides/itk_6_migration_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -1385,3 +1385,37 @@ if constexpr (!itk::ByteSwapper<unsigned short>::SystemIsBigEndian())

The same replacements apply to `CMAKE_WORDS_BIGENDIAN`. The query is usable in
`static_assert` and template arguments as well, since it is `constexpr`.

## `ITK_CXX_ABI` fixes the C++ standard that ITK headers present

`itkConfigure.h` defines `ITK_CXX_ABI` (17, 20, 23, or 26) from the C++ standard
ITK was configured with, and an `ITK_CXX_ABI_STD_<FEATURE>` macro for each
standard library feature ITK found and uses. ITK headers select
standard-dependent code from those macros and no longer from `__cplusplus` or
feature-test macros, so ITK and every client of one ITK build see the same
definitions. A client built as C++20
against an ITK built as C++17 now gets the C++17 form of those headers.

`ITK_CXX_ABI` is always the standard ITK was built with and cannot be set
separately. It is the lowest C++ standard a project using that ITK may compile
with, and `ITKConfig.cmake` exports it as the CMake variable `ITK_CXX_ABI`.

A project that uses a C++ standard older than C++17 or older than `ITK_CXX_ABI`
gets a compiler warning from `itkConfigure.h` in this release. The warning is an
`#error` when ITK is built with `ITK_FUTURE_LEGACY_REMOVE`, and becomes an
`#error` for everyone in the following release. A project whose standard
library lacks a feature that ITK recorded fails to compile.

### What you need to do

Build with at least the C++ standard of the ITK you link to. In a header that
must select code by C++ standard and that includes ITK headers, test
`ITK_CXX_ABI`:

```cpp
// Before
#if __cplusplus >= 202002L

// After
#if ITK_CXX_ABI >= 20
```
46 changes: 46 additions & 0 deletions Modules/Core/Common/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,52 @@ foreach(CXX_TEST_VERSION 201103L 201402L 201703L)
endif()
endforeach()

# Always the standard ITK is built with, never a separate setting: it is the floor for every client.
set(_itk_cxx_abi 17)
foreach(_itk_cxx_abi_candidate 20 23 26)
if(CMAKE_CXX_STANDARD GREATER_EQUAL _itk_cxx_abi_candidate)
set(_itk_cxx_abi ${_itk_cxx_abi_candidate})
endif()
endforeach()
unset(ITK_CXX_ABI)
set(
ITK_CXX_ABI
"${_itk_cxx_abi}"
CACHE INTERNAL
"C++ standard that ITK headers present to ITK and its clients"
FORCE
)
unset(_itk_cxx_abi)
unset(_itk_cxx_abi_candidate)

# Library features are part of the ABI level: probed on every configure, never by a client's feature-test macros.
include(CheckCXXSourceCompiles)
foreach(_itk_cxx_abi_feature ENDIAN MATH_CONSTANTS ATOMIC_REF)
unset(ITK_CXX_ABI_HAS_STD_${_itk_cxx_abi_feature} CACHE)
set(ITK_CXX_ABI_STD_${_itk_cxx_abi_feature} OFF)
endforeach()
unset(_itk_cxx_abi_feature)
if(ITK_CXX_ABI GREATER_EQUAL 20)
check_cxx_source_compiles(
"#include <bit>
int main() { return std::endian::native == std::endian::big ? 1 : 0; }"
ITK_CXX_ABI_HAS_STD_ENDIAN
)
check_cxx_source_compiles(
"#include <numbers>
int main() { return std::numbers::pi > 3.0 ? 0 : 1; }"
ITK_CXX_ABI_HAS_STD_MATH_CONSTANTS
)
check_cxx_source_compiles(
"#include <atomic>
int main() { signed char value = 0; return std::atomic_ref<signed char>(value).load(); }"
ITK_CXX_ABI_HAS_STD_ATOMIC_REF
)
set(ITK_CXX_ABI_STD_ENDIAN ${ITK_CXX_ABI_HAS_STD_ENDIAN})
set(ITK_CXX_ABI_STD_MATH_CONSTANTS ${ITK_CXX_ABI_HAS_STD_MATH_CONSTANTS})
set(ITK_CXX_ABI_STD_ATOMIC_REF ${ITK_CXX_ABI_HAS_STD_ATOMIC_REF})
endif()

try_compile(
ITK_HAS_GNU_ATTRIBUTE_ALIGNED
${ITK_BINARY_DIR}
Expand Down
7 changes: 2 additions & 5 deletions Modules/Core/Common/include/itkByteSwapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,7 @@
#include "itkObjectFactory.h"
#include "itkIntTypes.h"

#if __has_include(<version>)
# include <version>
#endif
#if defined(__cpp_lib_endian) // C++20 compiler support
#if defined(ITK_CXX_ABI_STD_ENDIAN)
# include <bit>
#endif

Expand Down Expand Up @@ -171,7 +168,7 @@ class ITK_TEMPLATE_EXPORT ByteSwapper : public Object
SwapWriteRange(const T * buffer, SizeValueType numberOfElements, std::ostream & outputStream);

static constexpr bool m_SystemIsBigEndian{
#if defined(__cpp_lib_endian) // C++20 compiler support
#if defined(ITK_CXX_ABI_STD_ENDIAN)
std::endian::native == std::endian::big
#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__)
__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
Expand Down
4 changes: 2 additions & 2 deletions Modules/Core/Common/include/itkMacro.h
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ destructor." (Visual Studio 2022/MSVC)
// the ability for operator!= to be rewritten automatically in terms of
// operator==, as introduced with C++20. This macro is experimental. It may be
// modified, renamed, or removed without backward compatibility support.
#if __cplusplus >= 202002L
#if ITK_CXX_ABI >= 20
# define ITK_EXPERIMENTAL_CXX20_REWRITTEN_UNEQUAL_OPERATOR
#endif

Expand Down Expand Up @@ -1687,7 +1687,7 @@ ContainerCopyWithCheck(MemberContainerType & m, const CopyFromContainerType & c,
# define ITK_FUTURE_DEPRECATED(message)
#endif

#if __cplusplus >= 202002L
#if ITK_CXX_ABI >= 20
# define ITK_NODISCARD(message) [[nodiscard(message)]]
#else
# define ITK_NODISCARD(message) [[nodiscard]]
Expand Down
10 changes: 4 additions & 6 deletions Modules/Core/Common/include/itkMath.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,11 @@
#include <cmath>
#include <limits>
#include <type_traits>
// _MSVC_LANG tracks /std: even when __cplusplus stays 199711L (no /Zc:__cplusplus),
// matching the STL condition under which <limits> defines __cpp_lib_math_constants.
#if (defined(__cplusplus) && __cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)
# include <numbers>
#endif
#include "itkMathDetail.h"
#include "itkConceptChecking.h"
#if defined(ITK_CXX_ABI_STD_MATH_CONSTANTS)
# include <numbers>
#endif
#if !defined(ITK_FUTURE_LEGACY_REMOVE)
// itk::Math no longer uses vnl_math; kept transitionally for downstream vnl_math:: users.
# include <vnl/vnl_math.h>
Expand Down Expand Up @@ -92,7 +90,7 @@ inline constexpr double sqrt1_3 = 0.57735026918962576451;
/** \brief euler constant */
inline constexpr double euler = 0.57721566490153286061;

#if defined(__cpp_lib_math_constants)
#if defined(ITK_CXX_ABI_STD_MATH_CONSTANTS)
// Bit-exactness proof against C++20 <numbers>. sqrt2pi and one_over_sqrt2pi have no
// exact std::numbers identity (inv_sqrtpi / sqrt2 double-rounds 1 ULP off).
static_assert(e == std::numbers::e);
Expand Down
37 changes: 26 additions & 11 deletions Modules/Core/Common/src/itkConfigure.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,11 @@
/* The CXX standard version used when ITK was built */
#define ITK_COMPILED_CXX_STANDARD_VERSION @ITK_COMPILED_CXX_STANDARD_VERSION@

/* Test external applications to ensure that compatible compiler C++ standard API are used
* by assuming that compiler standards are backwards (but not forwards) compatible with each
* other. Strange behaviors may occur if external applications do not use the same compiler
* and compiler language features (i.e. compiler flags).
* The warning is disabled for NVCC (NVidia CUDA) for use of ITK macros with CUDA versions
* prior to v7. */
#if !defined(__CUDACC__)
#if ITK_COMPILED_CXX_STANDARD_VERSION > __cplusplus
#warning "WARNING: The current project is configured to use a C++ standard version older than the C++ standard used for this build of ITK"
#endif
#endif
/* C++ standard (17, 20, 23, or 26) that selects standard-dependent code in ITK headers for ITK and every client. */
#define ITK_CXX_ABI @ITK_CXX_ABI@
#cmakedefine ITK_CXX_ABI_STD_ENDIAN
#cmakedefine ITK_CXX_ABI_STD_MATH_CONSTANTS
#cmakedefine ITK_CXX_ABI_STD_ATOMIC_REF

/* what threading system are we using */
#cmakedefine ITK_USE_PTHREADS
Expand All @@ -56,6 +50,27 @@
#cmakedefine ITK_LEGACY_REMOVE
#cmakedefine ITK_LEGACY_SILENT
#cmakedefine ITK_FUTURE_LEGACY_REMOVE

/* Compilers report draft standards with intermediate values, so each test is against the preceding standard. */
#if defined(__cplusplus) && !defined(__CUDACC__)
#if defined(_MSVC_LANG)
#define ITK_CLIENT_CPLUSPLUS _MSVC_LANG
#else
#define ITK_CLIENT_CPLUSPLUS __cplusplus
#endif
#if (ITK_CXX_ABI >= 26 && ITK_CLIENT_CPLUSPLUS <= 202302L) || \
(ITK_CXX_ABI >= 23 && ITK_CLIENT_CPLUSPLUS <= 202002L) || \
(ITK_CXX_ABI >= 20 && ITK_CLIENT_CPLUSPLUS <= 201703L) || ITK_CLIENT_CPLUSPLUS < 201703L
#if defined(ITK_FUTURE_LEGACY_REMOVE)
#error "This project uses a C++ standard older than the ITK_CXX_ABI of this build of ITK"
#elif defined(_MSC_VER)
#pragma message("warning: This project uses a C++ standard older than the ITK_CXX_ABI of this build of ITK")
#else
#warning "This project uses a C++ standard older than the ITK_CXX_ABI of this build of ITK"
#endif
#endif
#undef ITK_CLIENT_CPLUSPLUS
#endif
#cmakedefine ITK_USE_FFTWF
#cmakedefine ITK_USE_FFTWD
#cmakedefine ITK_USE_CUFFTW
Expand Down
Loading
Loading