Thursday, August 23, 2018

Understanding C++ Type Names with a Handy Function

C++ is a strongly-typed language. This means that every variable and expression has a type, and that type is known at compile-time. There are many times when we need to know the type of a variable or expression, especially when debugging or dealing with templates.

The C++ standard provides the typeid operator for this purpose. It gives us the type of an expression in the form of a std::type_info object, but it might not always give us the type in a human-readable format. For example, for the type int, it might return i instead of int.

To solve this problem, we can use a handy function, type_name(), which takes a template parameter T and returns a string containing the human-readable name of T. The code snippet above presents a definition of this function. Let's break it down:

Understanding the type_name() Function

The function type_name() is a template function that accepts a type T and returns its name as a std::string.

template <class T>
std::string type_name()
{
   //...
}

It first removes any reference qualifiers from T using std::remove_reference<T>::type:
typedef typename std::remove_reference<T>::type TR;

Then it demangles the name of TR using abi::__cxa_demangle if available. The function then constructs a string r with the demangled name, or typeid(TR).name() if demangling is not available.

Finally, it appends qualifiers such as const, volatile, and reference symbols (& or &&), if they exist:

#include <type_traits>
#include <typeinfo>
#ifndef _MSC_VER
#   include <cxxabi.h>
#endif
#include <memory>
#include <string>
#include <cstdlib>

template <class T>
std::string
type_name()
{
    typedef typename std::remove_reference<T>::type TR;
    std::unique_ptr<char, void(*)(void*)> own
           (
#ifndef _MSC_VER
                abi::__cxa_demangle(typeid(TR).name(), nullptr,
                                           nullptr, nullptr),
#else
                nullptr,
#endif
                std::free
           );
    std::string r = own != nullptr ? own.get() : typeid(TR).name();
    if (std::is_const<TR>::value)
        r += " const";
    if (std::is_volatile<TR>::value)
        r += " volatile";
    if (std::is_lvalue_reference<T>::value)
        r += "&";
    else if (std::is_rvalue_reference<T>::value)
        r += "&&";
    return r;
};


Using the type_name() Function

The type_name() function can be used like the following.

In this code, decltype is used to fetch the type of a variable or expression, and type_name() is used to print its human-readable name.


class Widget {
   public:
        int x, y;
};

auto f()
{
     int a = 9;
     cout << type_name<decltype(a)>() << endl;
     cout << type_name<decltype((a))>() << endl;
     return a;
}

int main(void)
{   
    Widget w;
    Widget &rw = w;
    auto aw = rw;
    auto daw = rw;
    cout << type_name<decltype(aw)>() << endl;
    cout << type_name<decltype(daw)>() << endl;
    f();
    return 0;
}

Running this code will output:

Widget
Widget&
int
int&

This output shows the type names of the variables aw, daw, a, and (a), respectively.

In conclusion, the type_name() function is a helpful tool for debugging and understanding C++ type information. It provides a human-readable type name and handles const, volatile, and reference qualifiers. It's a great addition to any C++ developer's toolkit.




No comments:

Post a Comment