Skip to content

ENH: Master tracking — virtual / override / final semantics on Get/Set macros for ITK 6.x #6232

@hjmjohnson

Description

@hjmjohnson

ENH: Master tracking — virtual / override / final semantics on Get/Set macros for ITK 6.x

Why this issue

Two long-stalled PRs (#2785 by @N-Dekker and #2568 by @jhlegarreta) and a 2018 Discourse thread on the adjacent NeighborhoodIterator question have established a pattern: the design conversation about whether ITK Get/Set macros should emit virtual lives only in scattered PR threads and has no GitHub Issue umbrella. As a result, individual incremental PRs keep getting filed without consensus on direction, and reviewers (especially @blowekamp) keep asking for a top-level discussion before more patches land.

This issue is a single venue to converge on what we want Get/Set polymorphism to look like in ITK 6 and 7, what is grandfathered, and what concrete sub-PRs the consolidated direction implies. ITKv6 is permitted to break or revise historical promises where the cost-benefit warrants it; the goal here is to make those revisions intentional, not accidental.

Historical baseline (load-bearing context)

Several decisions and observations were made deliberately and are documented across Discourse + Gerrit + GitHub:

  1. Removal of virtual from ConstNeighborhoodIterator is the empirical precedent. Discourse #814 (2018-03) is the canonical thread:

    • @N-Dekker: HoughTransform2DCirclesImageFilter went from > 10 minutes → < 30 seconds (40× speedup, ITK 4.13 vs ITK master with virtual removed from ConstNeighborhoodIterator).
    • @phcerdan independently reproduced an 18–24% reduction in HoughTransform run-time after the removal.
    • @blowekamp tried broad removal across all iterators and got 56 test failures including 15 SEGFAULTs (most caused by vtable-dependent code in ShapedNeighborhoodIterator).
    • @phcerdan: "the destructor of the bases should be virtual at least" — i.e., not all virtual is equal; removing only non-destructor virtual is the safe direction.
    • @blowekamp ended up using CRTP (Curiously Recurring Template Pattern) for the Shaped family — "a bit overkill, so if more elegant solutions suggested please propose."
  2. final-as-stress-test is a known technique (@N-Dekker, Discourse #814, post #4): "I first declared all of them 'final', as a WIP, just to be sure that they were not overridden anywhere in ITK." The same technique was used in the 2026-05-07 audit for Get/Set macros (see "Empirical override surface" below).

  3. Performance motivation is established for non-iterator hot paths too. Every virtual Get/Set call in a hot loop forces vtable indirection and prevents inlining — exactly the same compiler-optimization story as the iterator case.

The two stalled PRs

#2785@N-Dekker, 2021-09-12: "WIP: Remove `virtual` from Get/Set macro definitions"
  • 21 files modified across Modules/.
  • WIP because of @N-Dekker's own 2024-11 hesitation: "I find this a difficult issue. … Maybe also final if we want to ensure no derived class overrides."
  • Engagement: most recent activity Nov 2024.
  • No Fixes: linkage; no associated issue.
  • Effective design proposal: make non-virtual the default in the macros. ABI-breaking for any out-of-tree derived class that currently overrides; no breakage for callers.
#2568@jhlegarreta, 2020-06-25: "STYLE: Use macros to Set/Get ivars in `Common` operators"

The unmerged additive work (2026-05-07 session)

Branch hjmjohnson:macro-optionally-virtual (commit ffc70ed0b9, +40 lines, single file) adds:

  • A complete family of itkVirtual…Macro wrappers (28 aliases) — itkVirtualSetMacro, itkVirtualGetMacro, itkVirtualGetConstMacro, … — that delegate to the existing macros today. Marker macros at base-class sites. Zero behavior change, additive only.
  • The ITK_GETSET_VIRTUAL tripwire alongside ITK_ITERATOR_VIRTUAL — empty in ITK 6, static_assert(false, ...) under ITK_FUTURE_LEGACY_REMOVE to flag override sites that need attention.

Not opened as a PR yet pending the discussion in this issue.

Empirical override surface (2026-05-07 final-keyword audit)

Same final-as-stress-test technique @N-Dekker pioneered in 2018 was applied to the current Get/Set macros (every macro-emitted method declared final in a sandbox build):

  • 17 unique base methods are overridden by ≥ 1 derived class in upstream ITK.
  • 19 unique source files affected; 1305 total compile errors (template-instantiation amplification).
  • Top breakage sites: itkImageAdaptor.hxx (825 errors — single root cause: ImageBase::Get{Spacing,Origin,Direction}), itkImageToImageMetricv4.h (172), itkPointSetToPointSetMetricWithIndexv4.h (78), itkStatisticsImageFilter.h (63).

This validates @N-Dekker's 2024 hesitation empirically: removing virtual blindly would break ~17 distinct override sites. Conversely, most macros could go non-virtual safely with no downstream impact if the 17 known sites are migrated to explicit itkVirtualSetMacro first.

Questions that need answers

Q1. Should the Get/Set macros emit virtual by default?

  • Status quo: they do (e.g. itkSetMacro produces virtual void SetX(...)).
  • Option A (incremental): keep status quo; add itkVirtualSetMacro etc. as explicit-virtual variants for use at base-class sites that genuinely need polymorphism. New code uses non-virtual macros where appropriate; existing code unchanged. (My PR-A direction.)
  • Option B (default flip, ITK 6): make the default macros emit non-virtual. Add itkVirtualSetMacro for the 17 known override sites. ABI break for downstream code that overrides macro-emitted methods (rare; the audit identifies the upstream cases).
  • Option C (default flip, ITK 7): Option A in ITK 6 with ITK_FUTURE_LEGACY_REMOVE tripwire (ITK_GETSET_VIRTUAL); flip default in ITK 7 after a downstream-dashboard cycle confirms no out-of-tree breakage.

Q2. Should the destructor of base classes emit virtual?

@phcerdan's 2018 caveat: at least the destructor must remain virtual on polymorphic base classes. The Get/Set macros have nothing to do with destructors, but Q1 + #2785 must not accidentally remove virtual from a base destructor. Confirm the macros do not emit destructors.

Q3. CRTP for legitimately-polymorphic Get/Set sites?

Some base methods should be polymorphic (e.g. ImageAdaptor::GetSpacing proxies through the underlying image). For those:

  • Keep virtual. Use itkVirtualGetConstMacro so the intent is visible at the call site.
  • Or migrate to CRTP (per @blowekamp's 2018 NeighborhoodIterator pattern). CRTP is invasive; only worth the cost for genuine hot paths.

Decision needed per-site.

Q4. ITK_FUTURE_LEGACY_REMOVE tripwire — yes or no?

The ITK_GETSET_VIRTUAL marker (in PR-A) is the equivalent of ITK_ITERATOR_VIRTUAL. Empty in ITK 6, static_assert under ITK_FUTURE_LEGACY_REMOVE. This lets downstream consumers run the legacy-remove check on their dashboards before the default flip. Recommend adopting; needs maintainer ack.

Q5. Coordinate or supersede #2785 and #2568?

@N-Dekker is still engaged on #2785 (Nov 2024). @jhlegarreta is blocked on #2785 in #2568. Two paths:

Strong preference: coordinate.

Q6. Performance evidence for ITK 6?

@phcerdan's 2018 18-24% on HoughTransform was for ConstNeighborhoodIterator. Get/Set macros are not in equivalent hot paths, but the 17 known override sites include ImageBase::Get{Spacing,Origin,Direction} which IS hot. A small benchmark (e.g. PerformanceBenchmarking module) measuring registration / metrics with and without virtual on those three methods would justify or kill the urgency.

Q7. ITKv6 license to break

ITK 6 may revise historical promises. If the cost of ABI breakage on macro-emitted overrides is low (the 17 sites are all in-tree), Option B becomes viable. If maintainers have evidence of out-of-tree consumers that override macro-emitted methods, that constrains us toward Option C.

Maintainer positions summarized (best understanding)

Maintainer Position
@N-Dekker Original virtual-removal champion (NeighborhoodIterator 2018, Get/Set macros 2021); pioneered final-as-stress-test technique; 2024 hesitation: "this is a difficult issue"
@blowekamp Tried broad iterator removal in 2018, hit 56 test failures + 15 SEGFAULTs; introduced CRTP fallback for Shaped family; wants design discussion before more patches
@phcerdan Reproduced 18-24% perf gain in 2018; warned about base-class destructor virtuality
@jhlegarreta Blocked on #2785 (Q1 answer); willing reviewer when direction is settled
@dzenanz Permissive reviewer; would benefit from Q1 quorum to unblock incremental PRs
@hjmjohnson Filed this issue; authored PR-A macro-optionally-virtual; ran 2026-05-07 final-keyword audit

Concrete asks

  1. Each maintainer leaves a comment with their Q1 preference (A / B / C) and any non-obvious Q2–Q7 position.
  2. Once Q1 has a quorum:
  3. If Q3 surfaces hot-path Get/Set sites that warrant CRTP, open separate per-site PRs; do not bundle with the macro question.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions