Fun with template classes: Manipulating LabVIEW arrays in C++, part 2
This article is a follow-up on a previous article: 'Manipulating LabVIEW arrays in C++' in which I’ve explained how you could use C++ template classes to work with LabVIEW arrays. There was one annoying issue left to solve: you couldn’t use the standard syntax for indexing in a multidimensional array.
I.e. the following code would not work:
for(int row=0; row<requestCountRows; row++)
for(int column=0; column<requestCountColumns; column++)
Array[row][column] = i++;
After some thinking I was able to redesign the CArrayHandlePtr class to be able to be used like that.
The general idea
The idea behind my solution is to let the [x] operator for an n dimensional array return a new instance of the CArrayHandlePtr class that represents a class with n-1 dimensions of which the first element points to the first element of the subset pointed to by the [x] operator.
In that case, the operation Array[row][column] = 5 is equivalent to ((Array[row]) [column] = 5;
Array[row] returns a temporary 1D array temp. temp is then indexed like this: temp[column]. Since temp is a 1D array, it returns a reference to a specific element.
This approach is scalable to as many dimensions as you want, because a multidimensional index statement is simply broken down into n -1 temporary elements, each of which has a number of dimensional which has 1 dimension les than the previous one.
The implementation problem
The problem with this approach is that LabVIEW arrays do no lend themselves directly to it. The data block is supposed to begin directly after the dimension sizes. To return an array class of a lesser number of dimensions would mean that the selected subset would have to be preceded by the dimension sizes. This is clearly not an option.
To solve this problem we need array helper classes.
The helper classes
The point of a helper class is to be able to target a subset of a multidimensional array. To do this they should have a independent dimension size specifiers and data pointer.
Apart from that, the helper class has 1 simple requirement: the [] operator has to return a new helper of 1 dimension less than itself. If its number of dimensions is 1, it has to return a reference to the specified element.
The helper classes look like this:
template <class T, int Q=1> class CArrayHelper
{
private:
int32* m_Sizes;
T* m_Data;
public:
CArrayHelper(T* Data, int32 *Sizes)
{
m_Data = Data;
m_Sizes = Sizes;
}
CArrayHelper<T, Q-1> operator[](int Index)
{
int subsetSize = 1;
/*calculate the size of a complete subset*/
for(int i=1; i<Q; i++)
subsetSize *= m_Sizes[ i];
/*return an array helper that represents a subset of the original array*/
return CArrayHelper<T, Q-1>(m_Data + subsetSize * Index, m_Sizes +1);
}
};
template <class T> class CArrayHelper<T, 1>
{
private:
int32* m_Sizes;
T* m_Data;
public:
CArrayHelper(T* Data, int32 *Sizes)
{
m_Data = Data;
m_Sizes = Sizes;
}
T& operator[](int Index)
{
return m_Data[Index];
}
};
As you can see, these classes are very simple. Their only purpose is to be able to provide a way to specify a subset of a multidimensional array. That way the [] operators can be concatenated to read 1 specific element.
The actual array classes
There are 2 array classes: one general one and one that is specialized for 1 dimensional arrays. The reason that the specialization is needed is that the [] operator for a multidimensional array should return an array helper class of 1 dimension less, while the [] operator for a 1 dimensional array should return a reference to an element in the array.
template <class T, int Q=1> class CArrayHandlePtr
{
public:
/*type definition for LabVIEW array handle*/
#pragma pack(push)
#pragma pack(1)
typedef struct
{
int dimSize[Q];
T data[1];
} t_LvArray, *p_LvArray, **h_LvArray;
#pragma pack(pop)
h_LvArray* m_HandlePtr;
/*resize the array*/
int Resize(int DimSize[Q])
{
/*implementation omitted*/
}
T& operator[](int Index[Q])
{
/*implementation omitted*/
}
CArrayHelper<T, Q-1> operator[](int Index)
{
int subsetSize = 1;
/*calculate the size of a complete subset*/
for(int i=1; i<Q; i++)
subsetSize *= (**m_HandlePtr)->dimSize[ i];
/*return an array helper that represents a subset of the original array*/
return CArrayHelper<T, Q-1>
((T*)(**m_HandlePtr)->data + subsetSize * Index,
(int32*)(**m_HandlePtr)->dimSize +1);
}
};
template <class T> class CArrayHandlePtr<T, 1>
{
public:
/*type definition for LabVIEW array handle*/
typedef struct
{
int dimSize;
T data[1];
} t_LvArray, *p_LvArray, **h_LvArray;
h_LvArray* m_HandlePtr;
/*resize the array*/
void Resize(int DimSize)
{
/*implementation omitted*/
}
T& operator[](int Index)
{
return (**m_HandlePtr)->data[Index];
}
};
As you can see, the generic array class allow you to use either a complete element specification via the [](int[Q]) operator, or a partial specification via the [](int) operator. The partial specification returns an array helper. One way or another, you will reach a single array element.
Using the arrays
You now have both syntax options for addressing elements inside the array. You can do it like this:
for(int page=0; page<requestCountPages; page++)
for(int row=0; row<requestCountRows; row++)
for(int column=0; column<requestCountColumns; column++)
Array[page][row][column] = i++;
Or like this:
int index[3];
for(int page=0; page<requestCountPages; page++)
{
index[0] = page;
for(int row=0; row<requestCountRows; row++)
{
index[1] = row;
for(int column=0; column<requestCountColumns; column++)
{
index[2] = column;
Array[index] = i++;
}
}
}
Both have the same effect.
Afterthoughts
This solution is easier to use than my first LabVIEW array implementation, because you now have both syntax options for indexing in the array.
On top of that, I now know more about partial template specialization than before. It has been a lot of fun already. For my definition of fun at least.
I am still not satisfied with this solution though. We are now using a total of 4 classes, which have between them 5 [] operators and 2 implementations of the Resize function. Any method we want to add to the array class has to be implemented in both the generic and the specific class. There is also no compile time option to enable range checking. Nor has any attention been given to indexing performance optimization.
I think I will write another follow-up article to at least solve the multiple implementations problem.
As always, the complete dll and LabVIEW code can be downloaded used under the MIT license. Enjoy. [;-)]