C++ Get name of type in template

后端 未结 10 2051
天涯浪人
天涯浪人 2020-11-27 11:28

I\'m writing some template classes for parseing some text data files, and as such it is likly the great majority of parse errors will be due to errors in the data file, whic

相关标签:
10条回答
  • 2020-11-27 11:54

    The solution is

    typeid(T).name()
    

    which returns std::type_info.

    0 讨论(0)
  • 2020-11-27 11:56

    If you'd like a pretty_name, Logan Capaldo's solution can't deal with complex data structure: REGISTER_PARSE_TYPE(map<int,int>) and typeid(map<int,int>).name() gives me a result of St3mapIiiSt4lessIiESaISt4pairIKiiEEE

    There is another interesting answer using unordered_map or map comes from https://en.cppreference.com/w/cpp/types/type_index.

    #include <iostream>
    #include <unordered_map>
    #include <map>
    #include <typeindex>
    using namespace std;
    unordered_map<type_index,string> types_map_;
    
    int main(){
        types_map_[typeid(int)]="int";
        types_map_[typeid(float)]="float";
        types_map_[typeid(map<int,int>)]="map<int,int>";
    
        map<int,int> mp;
        cout<<types_map_[typeid(map<int,int>)]<<endl;
        cout<<types_map_[typeid(mp)]<<endl;
        return 0;
    }
    
    0 讨论(0)
  • 2020-11-27 11:57

    typeid(uint8_t).name() is nice, but it returns "unsigned char" while you may expect "uint8_t".

    This piece of code will return you the appropriate type

    #define DECLARE_SET_FORMAT_FOR(type) \
        if ( typeid(type) == typeid(T) ) \
            formatStr = #type;
    
    template<typename T>
    static std::string GetFormatName()
    {
        std::string formatStr;
    
        DECLARE_SET_FORMAT_FOR( uint8_t ) 
        DECLARE_SET_FORMAT_FOR( int8_t ) 
    
        DECLARE_SET_FORMAT_FOR( uint16_t )
        DECLARE_SET_FORMAT_FOR( int16_t )
    
        DECLARE_SET_FORMAT_FOR( uint32_t )
        DECLARE_SET_FORMAT_FOR( int32_t )
    
        DECLARE_SET_FORMAT_FOR( float )
    
        // .. to be exptended with other standard types you want to be displayed smartly
    
        if ( formatStr.empty() )
        {
            assert( false );
            formatStr = typeid(T).name();
        }
    
        return formatStr;
    }
    
    0 讨论(0)
  • 2020-11-27 11:58

    This trick was mentioned under a few other questions, but not here yet.

    All major compilers support __PRETTY_FUNC__ (GCC & Clang) /__FUNCSIG__ (MSVC) as an extension.

    When used in a template like this:

    template <typename T> const char *foo()
    {
        #ifdef _MSC_VER
        return __FUNCSIG__;
        #else
        return __PRETTY_FUNCTION__;
        #endif
    }
    

    It produces strings in a compiler-dependent format, that contain, among other things, the name of T.

    E.g. foo<float>() returns:

    • "const char* foo() [with T = float]" on GCC
    • "const char *foo() [T = float]" on Clang
    • "const char *__cdecl foo<float>(void)" on MSVC

    You can easily parse the type names out of those strings. You just need to figure out how many 'junk' characters your compiler inserts before and after the type.

    You can even do that completely at compile-time.


    The resulting names can slightly vary between different compilers. E.g. GCC omits default template arguments, and MSVC prefixes classes with the word class.


    Here's an implementation that I've been using. Everything is done at compile-time.

    Example usage:

    std::cout << TypeName<float>() << '\n';
    std::cout << TypeName(1.2f); << '\n';
    

    Implementation:

    #include <array>
    #include <cstddef>
    
    namespace impl
    {
        template <typename T>
        constexpr const auto &RawTypeName()
        {
            #ifdef _MSC_VER
            return __FUNCSIG__;
            #else
            return __PRETTY_FUNCTION__;
            #endif
        }
    
        struct RawTypeNameFormat
        {
            std::size_t leading_junk = 0, trailing_junk = 0;
        };
    
        // Returns `false` on failure.
        inline constexpr bool GetRawTypeNameFormat(RawTypeNameFormat *format)
        {
            const auto &str = RawTypeName<int>();
            for (std::size_t i = 0;; i++)
            {
                if (str[i] == 'i' && str[i+1] == 'n' && str[i+2] == 't')
                {
                    if (format)
                    {
                        format->leading_junk = i;
                        format->trailing_junk = sizeof(str)-i-3-1; // `3` is the length of "int", `1` is the space for the null terminator.
                    }
                    return true;
                }
            }
            return false;
        }
    
        inline static constexpr RawTypeNameFormat format =
        []{
            static_assert(GetRawTypeNameFormat(nullptr), "Unable to figure out how to generate type names on this compiler.");
            RawTypeNameFormat format;
            GetRawTypeNameFormat(&format);
            return format;
        }();
    }
    
    // Returns the type name in a `std::array<char, N>` (null-terminated).
    template <typename T>
    [[nodiscard]] constexpr auto CexprTypeName()
    {
        constexpr std::size_t len = sizeof(impl::RawTypeName<T>()) - impl::format.leading_junk - impl::format.trailing_junk;
        std::array<char, len> name{};
        for (std::size_t i = 0; i < len-1; i++)
            name[i] = impl::RawTypeName<T>()[i + impl::format.leading_junk];
        return name;
    }
    
    template <typename T>
    [[nodiscard]] const char *TypeName()
    {
        static constexpr auto name = CexprTypeName<T>();
        return name.data();
    }
    template <typename T>
    [[nodiscard]] const char *TypeName(const T &)
    {
        return TypeName<T>();
    }
    
    0 讨论(0)
  • 2020-11-27 12:03

    typeid(T).name() is implementation defined and doesn't guarantee human readable string.

    Reading cppreference.com :

    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.

    ...

    With compilers such as gcc and clang, the returned string can be piped through c++filt -t to be converted to human-readable form.

    But in some cases gcc doesn't return right string. For example on my machine I have gcc whith -std=c++11 and inside template function typeid(T).name() returns "j" for "unsigned int". It's so called mangled name. To get real type name, use abi::__cxa_demangle() function (gcc only):

    #include <string>
    #include <cstdlib>
    #include <cxxabi.h>
    
    template<typename T>
    std::string type_name()
    {
        int status;
        std::string tname = typeid(T).name();
        char *demangled_name = abi::__cxa_demangle(tname.c_str(), NULL, NULL, &status);
        if(status == 0) {
            tname = demangled_name;
            std::free(demangled_name);
        }   
        return tname;
    }
    
    0 讨论(0)
  • 2020-11-27 12:06

    Jesse Beder's solution is likely the best, but if you don't like the names typeid gives you (I think gcc gives you mangled names for instance), you can do something like:

    template<typename T>
    struct TypeParseTraits;
    
    #define REGISTER_PARSE_TYPE(X) template <> struct TypeParseTraits<X> \
        { static const char* name; } ; const char* TypeParseTraits<X>::name = #X
    
    
    REGISTER_PARSE_TYPE(int);
    REGISTER_PARSE_TYPE(double);
    REGISTER_PARSE_TYPE(FooClass);
    // etc...
    

    And then use it like

    throw ParseError(TypeParseTraits<T>::name);
    

    EDIT:

    You could also combine the two, change name to be a function that by default calls typeid(T).name() and then only specialize for those cases where that's not acceptable.

    0 讨论(0)
提交回复
热议问题