C ++获取模板中的类型名称

时间:2009-06-28 18:30:59

标签: c++ templates compile-time typename

我正在编写一些用于解析某些文本数据文件的模板类,因此很可能绝大多数解析错误都是由于数据文件中的错误造成的,这些错误大部分都不是由程序员编写的,所以需要一个关于为什么app无法加载的好消息,例如类似的东西:

  

解析example.txt时出错。 [MySectiom] Key的值(“notaninteger”)不是有效的int

我可以从传递给模板函数的参数和类中的成员变量中找出文件,节和键名,但是我不知道如何获取模板函数试图转换的类型的名称到。

我当前的代码看起来很像,只有普通字符串的特殊化:

template<typename T> T GetValue(const std::wstring &section, const std::wstring &key)
{
    std::map<std::wstring, std::wstring>::iterator it = map[section].find(key);
    if(it == map[section].end())
        throw ItemDoesNotExist(file, section, key)
    else
    {
        try{return boost::lexical_cast<T>(it->second);}
        //needs to get the name from T somehow
        catch(...)throw ParseError(file, section, key, it->second, TypeName(T));
    }
}

Id而不是必须为数据文件可能使用的每种类型进行特定的重载,因为它们有很多...

此外,我需要一个不会产生任何运行时开销的解决方案,除非发生异常,即完全编译时解决方案是我想要的,因为此代码被称为大量时间并且加载时间已经变得有点长。

编辑:好的,这是我提出的解决方案:

我有一个包含以下

的types.h
#pragma once
template<typename T> const wchar_t *GetTypeName();

#define DEFINE_TYPE_NAME(type, name) \
    template<>const wchar_t *GetTypeName<type>(){return name;}

然后我可以在cpp文件中使用DEFINE_TYPE_NAME宏来处理我需要处理的每种类型(例如,在定义要开始的类型的cpp文件中)。

然后链接器能够找到适当的模板特化,只要它在某处定义,否则抛出链接器错误,以便我可以添加类型。

10 个答案:

答案 0 :(得分:58)

解决方案是

typeid(T).name()

返回std::type_info

答案 1 :(得分:39)

typeid(T).name()是实现定义的,不保证人类可读的字符串。

阅读cppreference.com

  

返回一个实现定义的以null结尾的字符串   包含类型的名称。不给予任何保证   特别是,返回的字符串对于几种类型可以是相同的   在同一程序的调用之间进行更改。

     

...

     

使用gcc和clang之类的编译器,返回的字符串可以通过c ++ filt -t传输,以转换为人类可读的形式。

但在某些情况下,gcc不会返回正确的字符串。例如,在我的机器上,我有gcc whith -std=c++11,内部模板函数typeid(T).name()"j"返回"unsigned int"。这就是所谓的错位名称。要获得真实的类型名称,请使用 abi::__cxa_demangle()函数(仅限gcc):

#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;
}

答案 2 :(得分:35)

Jesse Beder的解决方案可能是最好的,但是如果你不喜欢typeid给你的名字(我认为gcc会给你提供错误的名字),你可以这样做:

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...

然后像

一样使用它
throw ParseError(TypeParseTraits<T>::name);

编辑:

您也可以将两者结合起来,将name更改为默认情况下调用typeid(T).name()的函数,然后仅专门针对那些不可接受的情况。

答案 3 :(得分:18)

正如Bunkar提到的,typeid(T).name是实现定义的。

要避免此问题,您可以使用Boost.TypeIndex库。

例如:

boost::typeindex::type_id<T>().pretty_name() // human readable

答案 4 :(得分:9)

Logan Capaldo的答案是正确的,但可以略微简化,因为每次都不需要专门化课程。人们可以写:

// in header
template<typename T>
struct TypeParseTraits
{ static const char* name; };

// in c-file
#define REGISTER_PARSE_TYPE(X) \
    template <> const char* TypeParseTraits<X>::name = #X

REGISTER_PARSE_TYPE(int);
REGISTER_PARSE_TYPE(double);
REGISTER_PARSE_TYPE(FooClass);
// etc...

这也允许您将REGISTER_PARSE_TYPE指令放在C ++文件中......

答案 5 :(得分:8)

重新描述安德烈的回答:

Boost TypeIndex库可用于打印类型名称。

在模板中,这可能如下所示

#include <boost/type_index.hpp>
#include <iostream>

template<typename T>
void printNameOfType() {
    std::cout << "Type of T: " 
              << boost::typeindex::type_id<T>().pretty_name() 
              << std::endl;
}

答案 6 :(得分:2)

typeid(uint8_t).name()很不错,但它返回“ unsigned char”,而您可能会期望“ uint8_t”。

这段代码将为您返回适当的类型

#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;
}

答案 7 :(得分:1)

我把它留在那里。 如果有人仍然需要它,那么你可以使用它:

template <class T>
bool isString(T* t) { return false;  } // normal case returns false

template <>
bool isString(char* t) { return true; }  // but for char* or String.c_str() returns true
.
.
.

这只会检查类型而不是GET,只适用于1种或2种。

答案 8 :(得分:0)

如果您要使用pretty_name,Logan Capaldo的解决方案将无法处理复杂的数据结构:REGISTER_PARSE_TYPE(map<int,int>)typeid(map<int,int>).name()给了我St3mapIiiSt4lessIiESaISt4pairIKiiEEE

的结果

使用unordered_mapmap的另一个有趣的答案来自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;
}

答案 9 :(得分:0)

这个技巧在其他几个问题中被提及,但在这里还没有。

所有主要编译器均支持__PRETTY_FUNC__(GCC和Clang)/ __FUNCSIG__(MSVC)作为扩展。

在这样的模板中使用时:

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

它产生依赖于编译器的格式的字符串,其中包括T的名称。

例如foo<float>()返回:

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

您可以轻松地从这些字符串中解析类型名称。您只需要弄清楚编译器在类型之前和之后插入多少“垃圾”字符。

您甚至可以在编译时完全做到这一点。


结果名称在不同的编译器之间可能略有不同。例如。 GCC省略了默认的模板参数,MSVC在类的前面加上了单词class


这是我一直在使用的实现。一切都在编译时完成。

用法示例:

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

实施:

#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>();
}