I have a data structure that looks roughly like this
struct Column
{
void* data;
};
template <class... T>
struct Table
{
size_t count;
std::vector<Column> columns; // columns.size() == sizeof...(T)
};
and I'm trying to visualize it in the following way
+ Table
+ Column 0
item 1
item 2
...
+ Column 1
item 1
item 2
...
This is what I have so far:
<Type Name="Table<*>">
<Expand>
<Synthetic Name="Column 0">
<Expand>
<ArrayItems>
<Size>count</Size>
<ValuePointer>($T1*) columns[0].data</ValuePointer>
</ArrayItems>
</Expand>
</Synthetic>
<Synthetic Name="Column 1" Condition="columns.size() > 1">
<Expand>
<ArrayItems>
<Size>count</Size>
<ValuePointer>($T2*) columns[1].data</ValuePointer>
</ArrayItems>
</Expand>
</Synthetic>
</Expand>
</Type>
Obviously, this scales really poorly. I'm relegated to copy-pasting the code for each column and adding a Condition
to enable or disable it. I'll end up with a maximum number of supported columns after which point the visualization just stops showing columns.
Is there some way to display this more intelligently? I can imagine a couple ways to do it if I could index the template parameter with an expression like $T$i
.
What I really want to do is something like this:
<Type Name="Table<*>">
<Expand>
<ArrayItems>
<Size>columns.size()</Size>
<Value>
<Synthetic Name="Column %i">
<Expand>
<ArrayItems>
<Size>count</Size>
<ValuePointer>($T$i2*) columns[$i2].data</ValuePointer>
</ArrayItems>
</Expand>
</Synthetic>
</Value>
</ArrayItems>
</Expand>
</Type>
It appears the only choice is to make a helper type in code that does recursive template expansion to peel off the template parameters one by one. And you have to force the compiler to instantiate the template so it's available for the natvis to use.
The data I'm starting with
struct ColumnStorage
{
void* data;
};
struct TableStorage
{
size_t rowCount;
std::vector<ColumnStorage> columns;
};
template <class Table, class... TableColumns>
struct TableAccessor
{
TableStorage* tableStorage;
};
And this is what I needed to add to get decent natvis
// The helper type that allows natvis to work. The first template parameter keeps track of the
// column index so we know where to index into TableStorage::columns. The second parameter isn't
// used here. The third parameter is the concrete types of each column.
template <int i, class Table, class... TableColumns>
struct NatvisColumnView;
template <class Table, class... TableColumns>
struct TableAccessor
{
TableStorage* tableStorage;
// Used by natvis to cast `this`
using NatvisView = NatvisColumnView<0, Table, TableColumns...>;
// Force the compiler to instantiate the template or it won't be available to natvis
TableAccessor() { (NatvisView*) this; }
};
// End the template recursion. Inherit from TableAccessor so that tableStorage can be used
template <int i, class Table, class Column>
struct NatvisColumnView<i, Table, Column> : TableAccessor<Table, Column> {};
// Recursive template to peel off column types one-by-one
template <int i, class Table, class FirstColumn, class... RestColumns>
struct NatvisColumnView<i, Table, FirstColumn, RestColumns...> : NatvisColumnView<i + 1, Table, RestColumns...>
{
using base = typename NatvisColumnView<i + 1, Table, RestColumns...>;
};
<Type Name="TableAccessor<*,*>">
<DisplayString>Table</DisplayString>
<Expand>
<Item Name="Count">tableStorage->rowCount</Item>
<!-- Cast `this` to the view type and use the for visualization -->
<ExpandedItem>*(NatvisView*) this</ExpandedItem>
</Expand>
</Type>
<!-- Bottom out the recursive view -->
<Type Name="NatvisColumnView<*,*,*>">
<DisplayString>NatvisColumnView</DisplayString>
<Expand>
<Synthetic Name="Column">
<Expand>
<ArrayItems>
<Size>tableStorage->rowCount</Size>
<ValuePointer>($T3*) tableStorage->columns[$T1].data</ValuePointer>
</ArrayItems>
</Expand>
</Synthetic>
</Expand>
</Type>
<!-- Display the first column then recurse -->
<Type Name="NatvisColumnView<*,*,*,*>">
<DisplayString>NatvisColumnView</DisplayString>
<Expand>
<Synthetic Name="Column">
<Expand>
<ArrayItems>
<Size>tableStorage->rowCount</Size>
<!-- Show the correct column using the column index (first template parameter)
and the column type (third template parameter) -->
<ValuePointer>($T3*) tableStorage->columns[$T1].data</ValuePointer>
</ArrayItems>
</Expand>
</Synthetic>
<ExpandedItem>*(base*) this</ExpandedItem>
</Expand>
</Type>
And it ends up looking something like this:
I tried various other approaches such as:
$T$i
.In the future, I'd go straight to a natvis DLL rather than fussing with the severely limited XML.