33
votes

I would like to use the name of a type at compile time. For example, suppose I've written:

constexpr size_t my_strlen(const char* s)
{
        const char* cp = s;
        while(*cp != '\0') { cp++; };
        return cp - s;
}

and now I want to have:

template <typename T>
constexpr auto type_name_length = my_strlen(typeid(T).name());

But alas, typeid(T).name() is just const char*, not constexpr... is there some other, constexpr way to get a type's name?

3
What do you intend to do with type_name_length<T> that you need it at compile time? Compilers are pretty good about just evaluating strlen() and giving you a constant if that's possible.Barry
@Barry: I just wanted an MCVE here, so I made up a synthetic use.einpoklum
@einpoklum That is good; but adding a comment saying that in the question (this is merely a MCVE, I am really trying to X) is also good.Yakk - Adam Nevraumont
@Yakk: I did say "for example" and "suppose"...einpoklum
@Yakk: It was for some CUDA device-side debugging code which prints type names but also needs to align the output, and I wanted to fit everything in a printf statement without loops to calculate lengths.einpoklum

3 Answers

96
votes

Well, you could, sort of, but probably not quite portable:

struct string_view
{
    char const* data;
    std::size_t size;
};

inline std::ostream& operator<<(std::ostream& o, string_view const& s)
{
    return o.write(s.data, s.size);
}

template<class T>
constexpr string_view get_name()
{
    char const* p = __PRETTY_FUNCTION__;
    while (*p++ != '=');
    for (; *p == ' '; ++p);
    char const* p2 = p;
    int count = 1;
    for (;;++p2)
    {
        switch (*p2)
        {
        case '[':
            ++count;
            break;
        case ']':
            --count;
            if (!count)
                return {p, std::size_t(p2 - p)};
        }
    }
    return {};
}

And you can define your desired type_name_length as:

template <typename T>
constexpr auto type_name_length = get_name<T>().size;

DEMO (works for clang & g++)

3
votes

Edit: Updated based on this answer to the non-constexpr-specific question; it is the result of refinements by several people including @HowardHinnant, @康桓瑋 @Val and myself.

The language standard does not - to my knowledge - provide any facility for obtaining type names. So, we resort to compiler-specific approaches. This works with GCC, clang and MSVC.

#include <string_view>
// If you can't use C++17's standard library, you'll need to use the GSL 
// string_view or implement your own struct (which would not be very difficult,
// since we only need a few methods here)

template <typename T> constexpr std::string_view type_name();

template <>
constexpr std::string_view type_name<void>()
{ return "void"; }

namespace detail {

using type_name_prober = void;

template <typename T>
constexpr std::string_view wrapped_type_name() 
{
#ifdef __clang__
    return __PRETTY_FUNCTION__;
#elif defined(__GNUC__)
    return __PRETTY_FUNCTION__;
#elif defined(_MSC_VER)
    return __FUNCSIG__;
#else
#error "Unsupported compiler"
#endif
}

constexpr std::size_t wrapped_type_name_prefix_length() { 
    return wrapped_type_name<type_name_prober>().find(type_name<type_name_prober>()); 
}

constexpr std::size_t wrapped_type_name_suffix_length() { 
    return wrapped_type_name<type_name_prober>().length() 
        - wrapped_type_name_prefix_length() 
        - type_name<type_name_prober>().length();
}

} // namespace detail

template <typename T>
constexpr std::string_view type_name() {
    constexpr auto wrapped_name = detail::wrapped_type_name<T>();
    constexpr auto prefix_length = detail::wrapped_type_name_prefix_length();
    constexpr auto suffix_length = detail::wrapped_type_name_suffix_length();
    constexpr auto type_name_length = wrapped_name.length() - prefix_length - suffix_length;
    return wrapped_name.substr(prefix_length, type_name_length);
}
-1
votes

An alternative answer that can be used in template, now it can run with g++ and clang++ and msvc.

Modified from Answer @einpoklum above: https://stackoverflow.com/a/56600402/12529885

#include <iostream>
#include <string_view>

template<typename T>
struct TypeName {
    constexpr static std::string_view fullname_intern() {
        #if defined(__clang__) || defined(__GNUC__)
            return __PRETTY_FUNCTION__;
        #elif defined(_MSC_VER)
            return __FUNCSIG__;
        #else
            #error "Unsupported compiler"
        #endif
    }
    constexpr static std::string_view name() {
        size_t prefix_len = TypeName<void>::fullname_intern().find("void");
        size_t multiple   = TypeName<void>::fullname_intern().size() - TypeName<int>::fullname_intern().size();
        size_t dummy_len  = TypeName<void>::fullname_intern().size() - 4*multiple;
        size_t target_len = (fullname_intern().size() - dummy_len)/multiple;
        std::string_view rv = fullname_intern().substr(prefix_len, target_len);
        if (rv.rfind(' ') == rv.npos)
            return rv;
        return rv.substr(rv.rfind(' ')+1);
    }

    using type = T;
    constexpr static std::string_view value = name();
};

namespace s1 {
    class MyClass;
}

//Both MSVC, G++ and Clang++ have passed test.
int main () {
    static_assert(TypeName<s1::MyClass>::value == "s1::MyClass");
    std::cout<<"FULLNAME> "<<TypeName<void>::fullname_intern()<<std::endl;
    std::cout<<"TYPETEST> '"<<TypeName<s1::MyClass>::value<<"' == 's1::MyClass'"<<std::endl;
    return 0;
}

Note that:

Full name in Clang++: static std::string_view TypeName<void>::fullname_intern() [T = void]

Full name in G++: static constexpr std::string_view TypeName<T>::fullname_intern() [with T = void; std::string_view = std::basic_string_view<char>]

Fullname in MSVC: class std::basic_string_view<char,struct std::char_traits<char> > __cdecl TypeName<void>::fullname_intern(void)(But 'class s1::MyClass' not 's1::MyClass' here)