92

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

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

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

我还需要一个解决方案,除非发生异常,否则不会产生任何运行时开销,即我想要一个完全编译时的解决方案,因为这段代码被调用了很多次并且加载时间已经变得有些长了。

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

我有一个 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 文件中)。

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

4

10 回答 10

91

解决方案是

typeid(T).name()

返回std::type_info

于 2009-06-28T18:42:03.033 回答
54

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

阅读cppreference.com

返回包含类型名称的实现定义的以空字符结尾的字符串。不提供任何保证,特别是,返回的字符串对于多种类型可以是相同的,并且在同一程序的调用之间会发生变化。

...

使用 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;
}
于 2013-10-01T18:59:26.660 回答
44

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()然后只针对那些不可接受的情况进行专门化。

于 2009-06-28T19:21:29.710 回答
20

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

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

例如:

boost::typeindex::type_id<T>().pretty_name() // human readable
于 2015-02-26T09:22:37.387 回答
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++ 文件中......

于 2016-04-28T21:58:19.483 回答
13

这个技巧在其他几个问题下提到过,但这里还没有。

所有主要编译器都支持__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]"在铿锵
  • "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>();
}
于 2019-12-29T19:54:01.800 回答
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;
}
于 2015-10-26T03:06:34.553 回答
2

如果你想要一个漂亮的名字,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;
}
于 2019-04-22T09:18:37.457 回答
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;
}
于 2020-02-14T13:35:18.530 回答
0

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

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。

于 2018-06-02T12:46:26.013 回答