I propose we introduce a few standard macros to be used in most image filters. These macros would define some relatively standard types commonly used in filters, such as ImageType
, PixelType
, ImageDimension
, Pointer
, ConstPointer
. It would save significant amount of boilerplate code in many filters. Here is an example of getting rid of such a macro in favor of classing type definitions. What do you think?
Not sure whether what the limits or criteria should be. Although avoiding the boilerplate is worth the definition of such macros, for certain types, such as ImageType
or PixelType
and their variants, I think it is useful/handy to have them directly in the class header. But just a personal opinion; not a strong one.
But I’d definitely use it for Pointer
and ConstPointer
.
The number of typedef and requires as boiler plate is cumbersome, and in consistent. However, I don’t like how “hidden” the macro makes the typedefs. It is already hard to figure out the actual types which are used, and this is additional obfuscation of the types.
An alternative would be to develop a ImageFilter “Traits” class. This would be a bit of a change, but hopefully for the better.
- Each Filter would define a “FilterTraits” *typedef.
template< typename TInputImage, typename TOutputImage = TInputImage >
struct ImageFilterTraits
{
using InputImageType = TInputImage;
using InputImagePointer = typename InputImageType::ConstPointer;
using InputImageRegionType = typename InputImageType::RegionType;
using InputImagePixelType = typename InputImageType::PixelType;
using OutputImageType = TOutputImage;
using OutputImagePointer = typename OutputImageType::Pointer;
using OutputImageRegionType = typename OutputImageType::RegionType;
using OutputImagePixelType = typename OutputImageType::PixelType;
};
template< typename TInputImage1, typename TInputImage2 = TInputImage1, typename TOutputImage = TInputImage1 >
class ITK_TEMPLATE_EXPORT SomeImageFilter:
public
ImageToImageFilter< TInputImage1, TOutputImage >
{
...
using ImageTraits = ImageFilterTraits<TInputImageType1, TOutputImage>;
typename ImageTraits::OutputImagePixelType SomeFunc( const typename ImageTraits::InputImagePixelType &in);
...
}
While this approach does provide a single location for typedef ( Do we still call them that when we use using
?) or aliases, it does seem to require the typename Traits::
qualification to use a type. So I’m not sure if it actually achieves the goals of class scoped types.
It think a simple macro that defines
Self
Superclass
Pointer
ConstPointer
can be applied nearly universally, would save errors in the definition of these, and would not obscure their definition since they are nearly universal.
Other typedefs are often not required or used, and they clutter the interface with irrelevant information. In most cases and for many typedef’s currently present, they do not need to be part of the public class interface, and they should be defined as they are used in the internal implementation.
@matt.mccormick These sound great! and universal! So it would be easy to add to the Doxygen configuration, for public clarity in the documentation.
I agree with using more local or function scoped typedef as opposed to class level. The case where they are useful is when the typedef
is used as part of the interface methods of the class, that is it is used as arguments or return value to methods.
On this topic, Luis reflected on the balance required with macros and typedefs – there is a fine line between Reusabillity and Knowing When to Stop:
Reusability
The principle of reusability can also be read as “avoidance of redundancy”. In the case of ITK, this has been achieved with a three-pronged approach.
First, the adoption of object-oriented programming, and in particular the proper creation of class hierarchies where common functionalities are factorized in base classes.
Second, the adoption of generic programming, implemented via the heavy use of C++ templates, factorizing behaviors that are identified as patterns.
Third, the generous use of C++ macros has also permitted reuse of standard snippets of code that are needed in myriad places across the toolkit.
Many of these items may sound like platitudes and appear obvious today, but when ITK development started in 1999 some of them were not that obvious. In particular, at the time the support most C++ compilers offered for templates did not quite follow a consistent standard. Even today, decisions such as the adoption of generic programming and the use of a widely templated implementation continue to be controversial in the community. This is manifested in the communities that prefer to use ITK via the wrapping layers to Python, Tcl or Java.
Knowing When to Stop
There is also the general risk of doing “too much of a good thing”, meaning, there is a risk of overusing templates, or overusing macros. It is easy to go overboard and end up creating a new language on top of C++ that is essentially based on the use of templates and macros. This is a fine line, and it demands continuous attention from the development team to make sure that the language features are properly used without being abused.
As a concrete example, the widespread use of explicitly naming types via C++ typedefs has proved to be particularly important. This practice plays two roles: on the one hand it provides a human-readable informative name describing the nature of the type and its purpose; on the other hand, it ensures that the type is used consistently across the toolkit. As an example, during the refactoring of the toolkit for its 4.0 version, a massive effort was invested in collecting the cases where C++ integer types such as int, unsigned int, long and unsigned long were used and to replace them with types named after the proper concept that the associated variables were representing. This was the most costly part of the task of ensuring that the toolkit was able to take advantage of 64-bit types for managing images larger than four gigabytes in all platforms. This task was of the utmost importance for promoting the use of ITK in the fields of microscopy and remote sensing, where image of tens of gigabytes in size are common.
From the ITK chapter of The Architecture of Open Source Applications.
I love the idea of reducing boilerplate, but I am not sure about introducing a macro for saving 2-6 lines of code. If that macro would be doing complex things or building/implementation details (thinking on Q_OBJECT in qt, or EXPORT_XXX in ITK), then yes, there is no better option.
Macros have a hit on readability, greater than those explicit boilerplate lines. Also tools are not very good parsing them.
But cleaning typedef from the public interface seems like a great idea. Is there any compiler option similar to Wunused-local-typedef
able to detect those?
If needed, we could also reduce variability in the typedef names changing:
using IndexType = typename Superclass::IndexType;
using MyNameForIndexType = typename Superclass::IndexType;
for
using typename Superclass:IndexType;