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

Fir*_*cer 69 c++ templates compile-time typename

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

解析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));
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

我有一个类型h包含以下内容

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

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

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

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

Jes*_*der 64

解决方案是

typeid(T).name()
Run Code Online (Sandbox Code Playgroud)

返回std :: type_info.

  • 请记住,它符合为每种类型返回相同的字符串(虽然我不认为任何编译器会这样做). (6认同)
  • 我只想指出给定的名字有多难看:`typeid(simd::double3x4).name() = "N4simd9double3x4E"`。`typeid(simd::float4).name() = "Dv4_f"` C++17,Xcode 10.1。 (5认同)
  • 或者在不同的执行中为相同类型返回不同的字符串...(同样,我认为任何理智的编译器都不会这样做)。 (3认同)
  • `typeid(T).name()` 不返回 `std::type_info`,而是返回 `char const *`。 (3认同)

小智 41

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;
}
Run Code Online (Sandbox Code Playgroud)

  • 这实际上适用于clang和gcc. (4认同)
  • 不,因为如果状态不为 0,则指针指向 `nullptr`。 (2认同)
  • 我想补充一点,最好检查 gcc 或 clang 的存在,如果不是,则默认不进行 demangling [如此处所示](http://ideone.com/sqFWir)。 (2认同)

Log*_*ldo 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...
Run Code Online (Sandbox Code Playgroud)

然后像使用它一样

throw ParseError(TypeParseTraits<T>::name);
Run Code Online (Sandbox Code Playgroud)

编辑:

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

  • 好吧,分离出分号是有意的,强迫你在宏调用结束时使用它,但感谢更正. (3认同)

And*_*rey 20

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

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

例如:

boost::typeindex::type_id<T>().pretty_name() // human readable
Run Code Online (Sandbox Code Playgroud)


rho*_*omu 11

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...
Run Code Online (Sandbox Code Playgroud)

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


Hol*_*Cat 11

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
}
Run Code Online (Sandbox Code Playgroud)

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';
Run Code Online (Sandbox Code Playgroud)

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>();
}
Run Code Online (Sandbox Code Playgroud)


chr*_*244 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;
}
Run Code Online (Sandbox Code Playgroud)