2

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&lt;*&gt;">
    <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&lt;*&gt;">
    <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>
Adam
  • 1,122
  • 9
  • 21
  • I think your problem isn't with natvis, you're trying to reimplement the tuple. As for `$T$i` - natvis is very simple, and `$T1`, `$T2` ... - are only macros – mr NAE Sep 19 '19 at 09:53
  • Maybe using Synthetic in this way is not a good idea, see [this](https://learn.microsoft.com/en-us/visualstudio/debugger/create-custom-views-of-native-objects?view=vs-2019#BKMK_Synthetic_Item_expansion)... – LoLance Sep 19 '19 at 10:41
  • No, I'm not trying to reimplement the tuple. Different use case. Different implementation. – Adam Sep 19 '19 at 15:59
  • @LanceLi-MSFT I can't tell why it's a bad idea from reading those docs. It appears to be the intended tool for creating additional hierarchy. I want to show an expansion for each column in the table. – Adam Sep 19 '19 at 16:06
  • Maybe you can also consider doing this with a bunch of condition statements and then hardcoding for different lengths. Of course there’d be a limit to the number you could show then. Something like: ”> [{this.m_ptr[0]}]/> [{this.m_ptr[0]}] [{this.m_ptr[1]}]/> 2”>[{this.m_ptr[0]}] [{this.m_ptr[1]}]…/> But glad to know you've found a workaround for your issue, you can mark it to share the useful info :) – LoLance Sep 20 '19 at 06:27
  • Yup, that's precisely what's in the in the OP. – Adam Sep 21 '19 at 07:13

1 Answers1

4

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&lt;*,*&gt;">
    <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&lt;*,*,*&gt;">
    <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&lt;*,*,*,*&gt;">
    <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: enter image description here enter image description here

I tried various other approaches such as:

  • Using IndexListItems. Doesn't work without being able to index the template parameters $T$i.
  • Using CustomListItems. Again, doesn't work without being able to index template parameters.
  • Using a variable templates in TableAccessor to get the type and index. Apparently natvis can't access variable templates.

In the future, I'd go straight to a natvis DLL rather than fussing with the severely limited XML.

Adam
  • 1,122
  • 9
  • 21
  • 1
    What does a "NatVis DLL" mean? Is it possible to write variable visualization for Visual Studio in C++ and make a DLL by building it? – Dr. Gut May 11 '20 at 00:29