In the codebase I'm working, we use std::any
instead of void*
to pass classes through some generic non-template code. Specifically, we use Visual Studio 2019, its compiler and standard library.
In order to visualize the std::any
, microsoft already gives a natvis:
<Type Name="std::any">
<Intrinsic Name="has_value" Expression="_Storage._TypeData != 0"/>
<Intrinsic Name="_Rep" Expression="_Storage._TypeData & _Rep_mask"/>
<Intrinsic Name="type" Expression="(const type_info*)(_Storage._TypeData & ~_Rep_mask)"/>
<Intrinsic Name="_Is_trivial" Expression="has_value() && _Rep() == 0"/>
<Intrinsic Name="_Is_big" Expression="has_value() && _Rep() == 1"/>
<Intrinsic Name="_Is_small" Expression="has_value() && _Rep() == 2"/>
<DisplayString Condition="!has_value()">[empty]</DisplayString>
<DisplayString Condition="_Is_trivial() || _Is_small()">[not empty (Small)]</DisplayString>
<DisplayString Condition="_Is_big()">[not empty (Large)]</DisplayString>
<Expand>
<Synthetic Name="has_value">
<DisplayString>{has_value()}</DisplayString>
</Synthetic>
<Synthetic Name="type" Condition="has_value()">
<DisplayString>{type()}</DisplayString>
</Synthetic>
<Synthetic Name="[representation]" Condition="_Is_trivial()">
<DisplayString>(Small/Trivial Object)</DisplayString>
</Synthetic>
<Synthetic Name="[representation]" Condition="_Is_small()">
<DisplayString>(Small Object)</DisplayString>
</Synthetic>
<Synthetic Name="[representation]" Condition="_Is_big()">
<DisplayString>(Dynamic Allocation)</DisplayString>
</Synthetic>
</Expand>
</Type>
However, this ends up showing us (Small Object)
instead of the std::string
that we have stored into it.
I've already managed to extend this with a few extra lines to get the pointer to the data:
<Item Name="[castable_ptr]" Condition="_Is_trivial()">(void*)(&_Storage._TrivialData)</Item>
<Item Name="[castable_ptr]" Condition="_Is_small()">(void*)(&_Storage._SmallStorage._Data)</Item>
<Item Name="[castable_ptr]" Condition="_Is_big()">(void*)(_Storage._BigStorage._Ptr)</Item>
However, this shows the data as a void*
, which you have to manually cast to a pointer of the actual type std::string*
.
However, this std::any
implementation/visualization also comes with a std::type_info
. (see field: type) that knows which underlying type we have.
Is there a way to use this std::type_info
so that the (void*)
can be replaced by a cast to the actual stored type?
EDIT: An example of the information that visual studio provides for the type: {mydll.dll!class std::tuple<__int64,double,double,double> 'RTTI Type Descriptor'} {...}
When explicitly casting the address to std::type_info*, I get access to _Data
in the debugger, that contains _DecoratedName
(.?AV?$tuple@_JNNN@std@@
) and _UndecoratedName
(nullptr
).
Unfortunately, I can't seem to find out how to write a cast that leverages this information.
I was not aware of the context (implementation constraints). If using std::any is identified as a strong prerequisite (which I can understand) and before Microsoft improves the native vizualizer, below could be a tactical quick win.
In the natvis xml configuration file:
<Synthetic Name="[representation]" Condition="_Is_trivial()">
<!--<DisplayString>(Small/Trivial Object)</DisplayString>-->
<DisplayString>{_Storage._TrivialData._Val}</DisplayString>
</Synthetic>
<Synthetic Name="[representation]" Condition="_Is_small()">
<!--<DisplayString>{*displayStdAnyContent(*this)}</DisplayString>-->
<Expand>
<Item Name="[value]">*displayStdAnyContent(*this)</Item>
</Expand>
</Synthetic>
<Type Name="AnyCastedValue<*>">
<DisplayString>{value}</DisplayString>
<Expand>
<Item Name="[value]">value</Item>
</Expand>
</Type>
C++
//----------------------------------------------------------------------------------
class IAnyValue
{
public:
virtual ~IAnyValue() = default;
};
template <typename T>
struct AnyCastedValue final :public IAnyValue
{
AnyCastedValue(const T& pi_value) :value(pi_value) {}
T value;
};
struct NonCastableValue final :public IAnyValue
{
static constexpr auto message = "I'm not castable! Please register my type!";
};
//----------------------------------------------------------------------------------
class IAnyCaster
{
public:
virtual std::unique_ptr<IAnyValue> castValue(const std::any& pi_any) const = 0;
virtual ~IAnyCaster() = default;
};
template <typename T>
class AnyCaster final :public IAnyCaster
{
public:
AnyCaster() = default;
virtual std::unique_ptr<IAnyValue> castValue(const std::any& pi_any) const override
{
return std::make_unique<AnyCastedValue<T>>(std::any_cast<T>(pi_any));
}
};
//----------------------------------------------------------------------------------
class AnyCasterService final :public std::unordered_map<std::string, std::unique_ptr<IAnyCaster>>
{
public:
AnyCasterService()
{
//here we register the types you would like be able to watch under the debugger
registerType<int>();
registerType<std::string>();
}
std::unique_ptr<IAnyValue> castValue(const std::any& pi_any) const
{
auto im = find(pi_any.type().name());
if (im != end())
{
return im->second->castValue(pi_any);
}
else return std::make_unique<NonCastableValue>();
}
private:
template <typename T>
void registerType()
{
this->insert(std::make_pair(typeid(T).name(),std::make_unique<AnyCaster<T>>()));
}
};
//----------------------------------------------------------------------------------
std::unique_ptr<IAnyValue> displayStdAnyContent(const std::any& pi_any)
{
static const AnyCasterService s_service;
return s_service.castValue(pi_any);
}
I validated this proposal under VS2017 v15.9.8
Keep in mind that Natvis could only evaluate the method at the runtime on your demand (explicit user call).
std::any l_any;
l_any = 5;
l_any = std::string("it works!");
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With