Skip to content

Commit 79841ed

Browse files
ENH: Add itk::print_helper::PrintNumericTrait helper
Add a non-macro replacement for the boilerplate os << indent << "Name: " << static_cast<typename NumericTraits<T>::PrintType>(this->m_Name) << std::endl; that frequently recurs in PrintSelf member-function overrides. The helper takes the output stream and indent as explicit parameters (no implicit capture from caller scope), uses constexpr dispatch to skip the cast when NumericTraits<T>::PrintType coincides with T (so std::complex specialisations and every built-in scalar wider than char go through the value's own stream insertion overload), and relies on the cast only for char-family types whose PrintType is int. Supersedes #3909 (which proposed a preprocessor macro). Reviewer preferences from that thread (no implicit capture, prefer named template/lambda over macro) are reflected in this design. Co-Authored-By: Jon Haitz Legarreta Gorroño <jhlegarreta@users.noreply.github.com>
1 parent d61861d commit 79841ed

2 files changed

Lines changed: 111 additions & 0 deletions

File tree

Modules/Core/Common/include/itkPrintHelper.h

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
#ifndef itkPrintHelper_h
2020
#define itkPrintHelper_h
2121

22+
#include "itkIndent.h"
23+
2224
#include <array>
2325
#include <iostream>
2426
#include <iterator>
@@ -27,13 +29,27 @@
2729
#include <type_traits>
2830

2931

32+
namespace itk
33+
{
34+
// Forward declaration so itkPrintHelper.h can be safely included from
35+
// itkMacro.h without re-entering itkNumericTraits.h (which itself uses
36+
// macros defined later in itkMacro.h). Every PrintNumericTrait() call
37+
// site needs the full NumericTraits<T> specialization in scope, but those
38+
// sites already #include "itkNumericTraits.h" directly or transitively.
39+
template <typename T>
40+
class NumericTraits;
41+
} // namespace itk
42+
3043
namespace itk::print_helper
3144
{
3245

3346
// Forward declarations so the per-container bodies below see all overloads at
3447
// definition time. Required for nested cases like vector<list<T>>, where the
3548
// recursive `os << *it` is parsed before the list overload would otherwise be
3649
// declared, and ADL on std container types never reaches itk::print_helper.
50+
// PrintNumericTrait further down also dispatches through `os << value` and
51+
// must see these forward declarations to instantiate against std container
52+
// member types at the call site.
3753
template <typename T>
3854
std::ostream &
3955
operator<<(std::ostream & os, const std::vector<T> & v);
@@ -50,6 +66,48 @@ template <typename T, size_t VLength, typename = std::enable_if_t<!std::is_same_
5066
std::ostream &
5167
operator<<(std::ostream & os, const T (&arr)[VLength]);
5268

69+
/** \brief Print "<name>: <value>\n" indented, matching ITK's PrintSelf style.
70+
*
71+
* Formats and writes a single named member to \a os preceded by \a indent,
72+
* followed by a newline. When \c NumericTraits<T>::PrintType differs from
73+
* \a T (the relevant case being the \c char family, whose \c PrintType is
74+
* \c int, so values render numerically rather than as ASCII characters) the
75+
* value is forwarded through a \c static_cast. When the two types coincide
76+
* (the common case, including all built-in scalars wider than \c char and
77+
* \c std::complex specialisations whose \c PrintType is \c Self) the cast
78+
* step is skipped entirely so the value's own stream insertion overload is
79+
* selected directly.
80+
*
81+
* Equivalent to the boilerplate
82+
* \code
83+
* os << indent << "Name: "
84+
* << static_cast<typename NumericTraits<T>::PrintType>(this->m_Name)
85+
* << std::endl;
86+
* \endcode
87+
* but with explicit \a os and \a indent parameters and no preprocessor
88+
* macro expansion.
89+
*
90+
* Typical use inside a \c PrintSelf override:
91+
* \code
92+
* itk::print_helper::PrintNumericTrait(os, indent, "Threshold", this->m_Threshold);
93+
* \endcode
94+
*/
95+
template <typename T>
96+
inline void
97+
PrintNumericTrait(std::ostream & os, const Indent & indent, const char * name, const T & value)
98+
{
99+
os << indent << name << ": ";
100+
if constexpr (std::is_same_v<T, typename NumericTraits<T>::PrintType>)
101+
{
102+
os << value;
103+
}
104+
else
105+
{
106+
os << static_cast<typename NumericTraits<T>::PrintType>(value);
107+
}
108+
os << std::endl;
109+
}
110+
53111
template <typename T>
54112
std::ostream &
55113
operator<<(std::ostream & os, const std::vector<T> & v)

Modules/Core/Common/test/itkPrintHelperGTest.cxx

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "itkOffset.h"
2121
#include "gtest/gtest.h"
2222
#include <array>
23+
#include <complex>
2324
#include <sstream>
2425
#include <vector>
2526
#include <list>
@@ -130,3 +131,55 @@ TEST(PrintHelper, ArrayOfVector)
130131
oss << a;
131132
EXPECT_EQ(oss.str(), "([1, 2], [3])");
132133
}
134+
135+
TEST(PrintHelper, PrintNumericTraitDouble)
136+
{
137+
std::ostringstream oss;
138+
itk::print_helper::PrintNumericTrait(oss, itk::Indent{}, "Value", 3.5);
139+
EXPECT_EQ(oss.str(), "Value: 3.5\n");
140+
}
141+
142+
TEST(PrintHelper, PrintNumericTraitIntIsIdentityCast)
143+
{
144+
// PrintType<int> == int, so the constexpr branch skips static_cast and
145+
// streams the value directly.
146+
std::ostringstream oss;
147+
itk::print_helper::PrintNumericTrait(oss, itk::Indent{}, "Count", 42);
148+
EXPECT_EQ(oss.str(), "Count: 42\n");
149+
}
150+
151+
TEST(PrintHelper, PrintNumericTraitCharRendersNumerically)
152+
{
153+
// PrintType<unsigned char> == int. Without the cast the value would be
154+
// emitted as the ASCII character; the helper must produce the integer.
155+
std::ostringstream oss;
156+
const unsigned char ch = 65;
157+
itk::print_helper::PrintNumericTrait(oss, itk::Indent{}, "Byte", ch);
158+
EXPECT_EQ(oss.str(), "Byte: 65\n");
159+
}
160+
161+
TEST(PrintHelper, PrintNumericTraitSignedCharRendersNumerically)
162+
{
163+
std::ostringstream oss;
164+
const signed char sc = -7;
165+
itk::print_helper::PrintNumericTrait(oss, itk::Indent{}, "Offset", sc);
166+
EXPECT_EQ(oss.str(), "Offset: -7\n");
167+
}
168+
169+
TEST(PrintHelper, PrintNumericTraitComplexIsIdentityCast)
170+
{
171+
// NumericTraits<std::complex<T>>::PrintType is Self, so PrintNumericTrait
172+
// forwards to the value's own ostream insertion overload unchanged.
173+
std::ostringstream oss;
174+
std::complex<double> z{ 1.0, -2.5 };
175+
itk::print_helper::PrintNumericTrait(oss, itk::Indent{}, "Z", z);
176+
EXPECT_EQ(oss.str(), "Z: (1,-2.5)\n");
177+
}
178+
179+
TEST(PrintHelper, PrintNumericTraitIndentation)
180+
{
181+
std::ostringstream oss;
182+
itk::Indent indent{ 4 };
183+
itk::print_helper::PrintNumericTrait(oss, indent, "Threshold", 0.125);
184+
EXPECT_EQ(oss.str(), " Threshold: 0.125\n");
185+
}

0 commit comments

Comments
 (0)