【问题标题】:C++ Get name of type in templateC ++获取模板中的类型名称
【发布时间】:2010-11-06 12:40:13
【问题描述】:

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

解析 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 文件中)。

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

【问题讨论】:

  • 与您的问题并不真正相关,但您可能还想在访问该部分时使用 map.find(section) ,除非您有意创建一个空白部分。

标签: c++ templates compile-time typename


【解决方案1】:

解决办法是

typeid(T).name()

返回std::type_info

【讨论】:

  • 请记住,为每种类型返回相同的字符串是合规的(尽管我认为任何编译器都不会这样做)。
  • 或者在不同的执行中为相同的类型返回不同的字符串......(我认为任何理智的编译器都会这样做)。
  • 我只想指出这个名字有多丑:typeid(simd::double3x4).name() = "N4simd9double3x4E"typeid(simd::float4).name() = "Dv4_f"C++17,Xcode 10.1。
  • 确实如此。 typeid(T).name() 是执行此操作的规范方法,但很少有编译器返回未损坏的名称;我个人熟悉的唯一一个这样做的是 MSVC。根据所使用的编译器,它也有可能丢失一些关于函数类型的类型信息,但在这种情况下可能无关紧要。
  • typeid(T).name() 不返回std::type_info,而是返回char const *
【解决方案2】:

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

阅读cppreference.com

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

...

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

【讨论】:

  • if 中有free 不是内存泄漏吗?
  • 否,因为如果状态不为0,指针指向nullptr
  • 我想补充一点,最好检查 gcc 或 clang 的存在,如果不是默认不进行解构as shown here
【解决方案3】:

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 的代码中)并且效果很好。
  • 由于“错误:非整数类型 'const char *' 的静态数据成员的类内初始化无效”,我不得不将名称移到 g++ 4.3.0 中的结构之外;当然,在 和 TypeParseTraits 之间需要关键字“struct”,并且定义应该以分号结束。
  • 省略分号是故意的,迫使您在宏调用结束时使用它,但感谢您的更正。
  • 我得到以下错误:error: '#' is not followed by a macro parameter
  • @kratsg - 那是因为最后 '#x' 应该是 '#X' (大写以匹配宏参数) - 我会修复答案。
【解决方案4】:

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

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

例如:

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

【讨论】:

  • 这对于在调用函数时找出模板类型名称非常有用。它对我来说效果很好。
  • 请注意, pretty_name() 或 raw_name() 仍然是实现定义的。在结构 A 的 MSVC 上;你会在 gcc/clang 上得到:“struct A”:“A”。
  • 哇。 boost 再次为胜利而战。令人惊讶的是,没有编译器支持(autoregexforeachthreadsstatic_assert 等......在编译器/C++ 标准支持之前支持)。
【解决方案5】:

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++ 文件中...

【讨论】:

    【解决方案6】:

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

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

    在这样的模板中使用时:

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

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

    例如foo&lt;float&gt;() 返回:

    • "const char* foo() [with T = float]" 在 GCC 上
    • "const char *foo() [T = float]" 在 Clang 上
    • "const char *__cdecl foo&lt;float&gt;(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>();
    }
    

    【讨论】:

      【解决方案7】:

      作为对安德烈回答的改写:

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

      【讨论】:

        【解决方案8】:

        如果你想要一个漂亮的名字,Logan Capaldo 的解决方案无法处理复杂的数据结构:REGISTER_PARSE_TYPE(map&lt;int,int&gt;) typeid(map&lt;int,int&gt;).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】:

          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:你说得对,这也行……
          【解决方案10】:

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

          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-03-10
            • 1970-01-01
            • 2010-10-10
            • 2011-03-10
            • 2011-05-05
            • 1970-01-01
            • 2016-02-14
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多