Unmangling the Result of Std::Type_Info::Name

Unmangling the result of std::type_info::name

Given the attention this question / answer receives, and the valuable feedback from GManNickG, I have cleaned up the code a little bit. Two versions are given: one with C++11 features and another one with only C++98 features.

In file type.hpp

#ifndef TYPE_HPP
#define TYPE_HPP

#include <string>
#include <typeinfo>

std::string demangle(const char* name);

template <class T>
std::string type(const T& t) {

return demangle(typeid(t).name());
}

#endif

In file type.cpp (requires C++11)

#include "type.hpp"
#ifdef __GNUG__
#include <cstdlib>
#include <memory>
#include <cxxabi.h>

std::string demangle(const char* name) {

int status = -4; // some arbitrary value to eliminate the compiler warning

// enable c++11 by passing the flag -std=c++11 to g++
std::unique_ptr<char, void(*)(void*)> res {
abi::__cxa_demangle(name, NULL, NULL, &status),
std::free
};

return (status==0) ? res.get() : name ;
}

#else

// does nothing if not g++
std::string demangle(const char* name) {
return name;
}

#endif

Usage:

#include <iostream>
#include "type.hpp"

struct Base { virtual ~Base() {} };

struct Derived : public Base { };

int main() {

Base* ptr_base = new Derived(); // Please use smart pointers in YOUR code!

std::cout << "Type of ptr_base: " << type(ptr_base) << std::endl;

std::cout << "Type of pointee: " << type(*ptr_base) << std::endl;

delete ptr_base;
}

It prints:

Type of ptr_base: Base*

Type of pointee: Derived

Tested with g++ 4.7.2, g++ 4.9.0 20140302 (experimental), clang++ 3.4 (trunk 184647), clang 3.5 (trunk 202594) on Linux 64 bit and g++ 4.7.2 (Mingw32, Win32 XP SP2).

If you cannot use C++11 features, here is how it can be done in C++98, the file type.cpp is now:

#include "type.hpp"
#ifdef __GNUG__
#include <cstdlib>
#include <memory>
#include <cxxabi.h>

struct handle {
char* p;
handle(char* ptr) : p(ptr) { }
~handle() { std::free(p); }
};

std::string demangle(const char* name) {

int status = -4; // some arbitrary value to eliminate the compiler warning

handle result( abi::__cxa_demangle(name, NULL, NULL, &status) );

return (status==0) ? result.p : name ;
}

#else

// does nothing if not g++
std::string demangle(const char* name) {
return name;
}

#endif


(Update from Sep 8, 2013)

The accepted answer (as of Sep 7, 2013), when the call to abi::__cxa_demangle() is successful, returns a pointer to a local, stack allocated array... ouch!

Also note that if you provide a buffer, abi::__cxa_demangle() assumes it to be allocated on the heap. Allocating the buffer on the stack is a bug (from the gnu doc): "If output_buffer is not long enough, it is expanded using realloc." Calling realloc() on a pointer to the stack... ouch! (See also Igor Skochinsky's kind comment.)

You can easily verify both of these bugs: just reduce the buffer size in the accepted answer (as of Sep 7, 2013) from 1024 to something smaller, for example 16, and give it something with a name not longer than 15 (so realloc() is not called). Still, depending on your system and the compiler optimizations, the output will be: garbage / nothing / program crash.

To verify the second bug: set the buffer size to 1 and call it with something whose name is longer than 1 character. When you run it, the program almost assuredly crashes as it attempts to call realloc() with a pointer to the stack.


(The old answer from Dec 27, 2010)

Important changes made to KeithB's code: the buffer has to be either allocated by malloc or specified as NULL. Do NOT allocate it on the stack.

It's wise to check that status as well.

I failed to find HAVE_CXA_DEMANGLE. I check __GNUG__ although that does not guarantee that the code will even compile. Anyone has a better idea?

#include <cxxabi.h>

const string demangle(const char* name) {

int status = -4;

char* res = abi::__cxa_demangle(name, NULL, NULL, &status);

const char* const demangled_name = (status==0)?res:name;

string ret_val(demangled_name);

free(res);

return ret_val;
}

Why does typeid.name() return weird characters using GCC and how to make it print unmangled names?

The return of name is implementation defined : an implementation is not even required to return different strings for different types.

What you get from g++ is a decorated name, that you can "demangle" using the c++filt command or __cxa_demangle.

Make a function in the base class aware of the class of the object calling it

Simple solution, replace type(this) with type(*this).
It works, although I don't know how to explain it.

Strange behaviour of the typeid operator?

What's going on is nothing special. Just that typeid doesn't promise to return the "original" name of the type, but just a name.

The function returns an implementation-defined string, which, if you're lucky, is recognizable, but it makes no promise of that.

Why is type_info::name() unspecified?

Basically, if an implementation decides that they can't or doesn't want to support RTTI, they can just return "";. If the standard forced it to return something, they'd possibly kill any ability to have a compliant compiler for an environment where the resources for RTTI don't exist or want to be disabled (a microchip, for example.)

And let's not forget we don't want to force an ABI/name-mangling scheme on any compilers.

This follows the C++ philosophy "You don't pay for things you don't need."

Readable form of typeid?

 typeid(var).name()

is what you are looking for. The output differs from compiler to compiler though... For gcc the output for int is i, for unsigned is j, for example. Here is a small test program:

#include <iostream>
#include <typeinfo>

struct A { virtual ~A() { } };
struct B : A { };

class C { };
class D : public C { };

int main() {
B b;
A* ap = &b;
A& ar = b;
std::cout << "ap: " << typeid(*ap).name() << std::endl;
std::cout << "ar: " << typeid(ar).name() << std::endl;

D d;
C* cp = &d;
C& cr = d;
std::cout << "cp: " << typeid(*cp).name() << std::endl;
std::cout << "cr: " << typeid(cr).name() << std::endl;

int e;
unsigned f;
char g;
float h;
double i;
std::cout << "int:\t" << typeid(e).name() << std::endl;
std::cout << "unsigned:\t" << typeid(f).name() << std::endl;
std::cout << "char:\t" << typeid(g).name() << std::endl;
std::cout << "float:\t" << typeid(h).name() << std::endl;
std::cout << "double:\t" << typeid(i).name() << std::endl;
}

See also this question: typeid(T).name() alternative in c++11?

How is type_info implemented

type_info is just a standard library class that provides type information. Objects of this class are returned by the typeid operator.

Of greatest interest is not the class itself, but the RTTI (Run-Time Type Identification) implementation. This is a purely compiler dependent feature, part of the ABI (Application Binary Interface).

In brief, the compiler stores type information for each polymorphic type along with its vtable or VMT (Virtual Method Table). This information is per-type, not per-object and used by typeid and by dynamic_cast. The type_info class is just an interface provided to the end user, it has an internal implementation depending on the compiler.

Different compilers implement different ABIs. Modern gcc and clang compilers implement Itanium C++ ABI, which describes all the details of RTTI and the rest. Microsoft Visual C++ ABI is undocumented.

A good article that describes C++ vtables and covers RTTI: Shahar Mike - C++ vtables.

Diffrent typeid between platforms

The typeid operator returns the name from the std::type_info, which is described as

The class type_info holds implementation-specific information about a type, including the name of the type and means to compare two types for equality or collating order. This is the class returned by the typeid operator.

So it is implementation-specific how they choose to name the types. This is reinforced again in std::type_info::name

Returns an implementation defined null-terminated character string containing the name of the type. No guarantees are given; in particular, the returned string can be identical for several types and change between invocations of the same program.

Getting incorrect class name when using typeid -

There is no requirement on what type_info::name() looks like.

The result of a typeid expression is an lvalue of static type const std::type_info (18.7.1) and dynamic type
const std::type_info or const name where name is an implementation-defined class publicly derived from
std::type_info

Then, about std::type_info::name():

const char* name() const;

Returns: an implementation-defined NTBS.

[...]

NTBS is simply a shorthand for null-terminated byte string.

In other words: You should not rely on any value of type_info::name().

What you actually see with g++:

Those names are mangled names, and g++'s implementation of such mangled names is based on length-prefixed strings, where each substring is the namespace name, plus some other info; but that's basically it.

For example:

unmangled: foo::bar::Frob
mangled: 3foo3bar4Frob

Example to put into your compiler:

#include <iostream>
#include <typeinfo>

namespace foo { namespace bar {
enum Frob {};
class Frobnicate {};
Frob frob;

template <typename T> void Meh() { throw T(); }
} }

int main () {
std::cout << typeid(foo::bar::Frob).name() << '\n'
<< typeid(foo::bar::Frobnicate).name() << '\n'
<< typeid(foo::bar::frob).name() << '\n'
<< typeid(foo::bar::Meh<int>).name() << '\n'
<< typeid(foo::bar::Meh<float>).name() << '\n'
;
}

Output for me:

N3foo3bar4FrobE
N3foo3bar10FrobnicateE
N3foo3bar4FrobE
FvvE
FvvE

The latter two show you that one even cannot rely on names being different.



Related Topics



Leave a reply



Submit