Currently, I've implemented a template class Mat that's a wrapper around the matrix class of a third party library (T is the type of the components: double, int, etc.). Now, I wish to implement a Tensor class uses one Mat for storage and a second Mat for mapping indices.
I've templated the Tensor class as Tensor where T is the same as for the Mat class, Order and Dim are integers and are the order (or rank) and dimension (2 or 3) of the tensor. Sym is a boolean flag for symmetry.
The Tensor class will make use of Voigt notation to condense higher-order tensors onto matrices (for example, a 3-by-3-by-3-by-3 tensor can be mapped to a 6-by-6 matrix; this is done by mapping each pair of indices to a single index). In Voigt notation, a 3-by-3 tensor can be mapped to a 6-by-1 vector (matrix) but moving the (0,0) component in the tensor to the 0-location in the vector. similar (1,1) -> 1, (2,2) -> 2, (1,2)-> 3, (0,2)-> 4, and (0,1)-> 5. A similar rule exits for 2-by-2 tensors (they map to a 3-by-1 matrix).
To this end, I'd like my Tensor class to own the matrix:
0 5 4
5 1 3
4 3 2
If Dim == 3 and Sym == true. There are corresponding maps for unsymmetric tensors and 2D tensors (4 in all). These don't depend on the other template parameters (T and Order).
Thus, at what point do I specialize them? (Here, the question becomes applicable to anyone who has a templated class that needs a static const member to only partial specializations).
I've checked out this question here: Where to define static const member variables of a template class. But it doesn't discuss partial specialization.
So far, I have a forward declaration and a class definition in the same header file:
//cl_Tensor.hpp
namespace myNamespace
{
template< typename T, int Order, int Dim, bool Sym >
class Tensor;
}
template< typename T, int Order, int Dim, bool Sym >
class myNamespace::Tensor
{
protected:
myNamespace::Mat< T> mMat; // storage
static const myNamespace::Mat < uint > mTensorMap;
public:
// member functions and the like...
}
In my unit tests for my Tensor class, I can type:
template<> const moris::Mat< moris::uint> moris::Tensor< moris::real, 1, 2, true>::mTensorMap = { { 0, 2}, {2, 1} };
template<> const moris::Mat< moris::uint> moris::Tensor< moris::real, 1, 2, false>::mTensorMap = { { 0, 3}, {2, 1} };
template<> const moris::Mat< moris::uint> moris::Tensor< moris::real, 1, 3, true>::mTensorMap = { { 0, 5, 4}, {5, 1, 3}, {4, 3, 2} };
template<> const moris::Mat< moris::uint> moris::Tensor< moris::real, 1, 3, false>::mTensorMap = { { 0, 5, 4}, {8, 1, 3}, {7, 6, 2} };
The problem is that I have to do this for every order (1, 2, 3, and 4). And should I have Tensors of other types (here, real is a typdef for long double), I will have too much duplicate code.
Where can I initialize the maps then?
I was able to go off the other answer and came up with the following solution. In the Tensor header file, I only declare the static const in the class definition. Afterwards, I template the member initialization using a MapCreator class.
//Tensor.hpp
template< typename T, int Order, int Dim, bool Sym >
class space::Tensor
{
protected:
Mat< T> mStorageMat;
static const Mat < unsigned int > mTensorMap;
public:
// ...
};
template< typename T, int Order, int Dim, bool Sym >
const space::Mat< unsigned int> space::Tensor<T, Order, Dim, Sym>::mTensorMatp = space::TensorMapCreator< Dim, Sym>::makeMap();
Then, the TensorMapCreator class is only templated for the two arguments that my member variable depends on:
//TensorMapCreator.hpp
namespace space {
template< int Dim, bool Sym>
class TensorMapCreator;
class TensorMapCreator< 2, true >; // specialized forward
class TensorMapCreator< 2, false>; // declarations
class TensorMapCreator< 3, true >;
class TensorMapCreator< 3, false>;
}
class space::TensorMapCreator< 2, true >
{
public:
static
space::Mat< unsigned int>
makeMap()
{
// creates the correct map
}
};
// 3 more specializations for the other
// combinations of dimmension and symmetry