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


76

我正在编写一些用于解析某些文本数据文件的模板类,因此很可能大部分解析错误是由于数据文件中的错误而引起的,这些错误大部分不是程序员编写的,因此需要关于应用为何无法加载的好消息,例如:

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

我可以从传递给模板函数的参数以及类中的成员vars得出文件名,节名和键名,但是我不确定如何获取模板函数尝试转换为的类型的名称。

我当前的代码看起来像,只对纯字符串等进行了专门化处理:

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

宁可不必对数据文件可能使用的每种类型都进行特定的重载,因为它们有很多负载...

我还需要一个解决方案,除非出现异常,否则不会产生任何运行时开销,即我想要一个完全编译时的解决方案,因为此代码被称为“成千上万次”,并且加载时间已经有些长了。

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

我有一个type.h包含以下内容

#pragma once
template<typename T> const wchar_t *GetTypeName();

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

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

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


1
并非与您的问题真正相关,但您也可能在访问该节时也要使用map.find(section),除非您有意创建一个空节。
伊丹·K

Answers:


40

杰西·贝德(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()然后仅专门处理不可接受的情况。


注意:如果您忘记为使用的类型定义REGISTER_PARSE_TYPE,则此代码将不会编译。我之前使用过类似的技巧(在没有RTTI的代码中),并且效果很好。
汤姆·莱斯

1
由于“错误:非整数类型'const char *'的静态数据成员的类内初始化无效”,我不得不在g ++ 4.3.0中将名称移出结构。当然,在<>和TypeParseTraits之间需要关键字'struct',并且定义应以分号终止。
FuzzyTew

4
好吧,将分号排除在外是有意的,迫使您在宏调用结束时使用它,但是感谢您的更正。
Logan Capaldo

我得到以下错误:error: '#' is not followed by a macro parameter
kratsg 2015年

@kratsg-这是因为最后'#x'应该为'#X'(大写以匹配宏参数)-我将解决问题。
2015年

69

解决方法是

typeid(T).name()

返回std :: type_info


6
请记住,为每种类型返回相同的字符串是合规的(尽管我认为任何编译器都不会这样做)。
Motti

3
或在不同的执行中为同一类型返回不同的字符串...(同样,我认为任何理智的编译器都不会这样做)。
艾米丽·L.

3
我只想指出该给定名称的丑陋程度:typeid(simd::double3x4).name() = "N4simd9double3x4E"typeid(simd::float4).name() = "Dv4_f"C ++ 17,Xcode 10.1。
安德烈亚斯(Andreas)

1
确实。 typeid(T).name()是这样做的规范方法,但是很少有编译器返回未修改的名称;我个人唯一熟悉的是MSVC。根据所使用的编译器,还可能会丢失一些有关函数类型的类型信息,但是在这种情况下这可能是无关紧要的。
贾斯汀时间-恢复莫妮卡

45

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

阅读cppreference.com

返回包含类型名称的实现定义的以null结束的字符串。没有任何保证,特别是,返回的字符串对于几种类型可以是相同的,并且在同一程序的调用之间可以改变。

...

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

但是在某些情况下,gcc不会返回正确的字符串。例如我的机器上我有GCC蒙山-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;
}

1
是不是内存泄漏freeif
托马什Zato -恢复莫妮卡

2
否,因为指针指向nullptr如果状态不为0
亨利·施赖纳

1
我想补充一点,可能最好是检查gcc或clang的存在,如果不是默认值,则不进行如下所示的拆分。
骆驼之神

20

如Bunkar所述,typeid(T).name是实现定义的。

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

例如:

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

这对于在调用函数时查找模板类型名称非常有用。对我来说效果很好。
Fernando

1
请注意,pretty_name()或raw_name()仍是实现定义的。在MSVC上获取结构A;在gcc / clang上,您将得到:“ struct A”:“ A”。
daminetreg

哇。boost再次争取胜利。惊人的是什么呢提升没有编译器的支持(autoregexforeachthreadsstatic_assert,等,等......前的编译器/ C ++的支持-标准支持)。
Trevor Boyd Smith,

14

洛根·卡帕尔多(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 ++文件中...


8

改写安德烈的答案:

升压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;
}

5

在其他一些问题中提到了这个技巧,但在这里还没有提到。

所有主要编译器均支持__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]" 在海湾合作委员会
  • "const char *foo() [T = float]" 在C
  • "const char *__cdecl foo<float>(void)" 在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>();
}

2

如果你想要一个pretty_name,洛根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;
}

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

这很好,但是为什么不做return #type;呢?
小帮手

@LittleHelper:你是对的,那也可以...
jpo38

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

这只会检查类型而不是获取类型,并且仅用于1种或2种类型。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.