I need to define a C++ template that accepts several 3D coordinates as their parameters.
Why do I need this? The motivation is to create an "accessor" template for doing implicit index transformations in 3D space based on several parameters already known at compile-time, which has several useful applications. For a hypothetical example, a template may support the following feature:
Origin transformation: A 3D array may have been created with the origin centered around
(0, 0, 0)
. But if some formulas or algorithms are designed to work with 1-based arrays, one can use a template with theoffset
parameter set to(-1, -1, -1)
. Similarly, if there's a need to recenter the origin at(20, 20, 20)
, one can use a template with theoffset
parameter set to(20, 20, 20)
.Padding: Sometimes, the dimension of the 3D space must be a multiple of a certain size, in this case, one can use a template with the
padding
parameter set to(4, 4, 4)
.Blocking: Sometimes, it's more efficient to store values in the 3D space in a tiled or blocked format, so a
blocking
parameter can be used to specify the block size.
To create such a template, my first attempt is to declare it as:
template <
int offset_i = 0,
int offset_j = 0,
int offset_k = 0,
int padding_i = 0,
int padding_j = 0,
int padding_k = 0,
int blocking_i = 0,
int blocking_j = 0,
int blocking_k = 0
>
class Space;
But as you see, when all dimensions of these coordinates are defined as separate integer variables, the parameter list would become exceedingly long - 3 coordinates need 9 parameters. This makes it hard to use, especially when the full type name of the template must be typed out with a lot of typing. For example, if a member function needs to return a 3D space, its signature would look like this:
Space<
offset_i,
offset_j,
offset_k,
padding_i,
padding_j,
padding_k,
blocking_i,
blocking_j,
blocking_k
> member_function(void);
Thus, it's highly desirable to declare the templates in a way to use compile-time arrays, instead of compile-time integers. For convenience, it's the best if their default values can be declared directly at the location of the template declaration as values, rather than as variable names that are declared elsewhere.
In C++20, the following solution became possible:
template <
std::array<int, 3> offset = std::array<int, 3>{0, 0, 0},
std::array<int, 3> padding = std::array<int, 3>{0, 0, 0},
std::array<int, 3> blocking = std::array<int, 3>{0, 0, 0}
>
class Space;
This has a much higher readability. It also simplifies the signature of member functions to:
Space<offset, padding, blocking> member_function(void);
A proposed C++ standard change would simplify it even further, to:
template <
std::array<int, 3> offset={0, 0, 0},
std::array<int, 3> padding={0, 0, 0},
std::array<int, 3> blocking={0, 0, 0}
>
class Space;
At this point, the original problem has already been solved in a very satisfactory manner.
However, this solution requires C++20, which may be unavailable in systems with older compiler versions.
Question
Is there an alternative and more compatible way to achieve the same code readability goal using other techniques already available in earlier C++ versions, such as C++17? Using std::array
is not necessary, any other methods will similar readability improvement are acceptable.
Use type parameters that have non-type parameters.
You can constrain template parameters of
Space
to be instantiations ofarray3
using SFINAE if you want.where
is_array3_v
is defined as