C++ 获取模板中的类型名称
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1055452/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
C++ Get name of type in template
提问by Fire Lancer
I'm writing some template classes for parseing some text data files, and as such it is likly the great majority of parse errors will be due to errors in the data file, which are for the most part not written by programmers, and so need a nice message about why the app failed to load e.g. something like:
我正在编写一些模板类来解析一些文本数据文件,因此很可能绝大多数解析错误是由于数据文件中的错误引起的,这些错误大部分不是由程序员编写的,因此需要关于为什么应用程序无法加载的好消息,例如:
Error parsing example.txt. Value ("notaninteger")of [MySectiom]Key is not a valid int
解析 example.txt 时出错。[MySectiom]Key 的值 ("notaniteger") 不是有效的整数
I can work out the file, section and key names from the arguments passed to the template function and member vars in the class, however I'm not sure how to get the name of the type the template function is trying to convert to.
我可以从传递给类中的模板函数和成员变量的参数中计算出文件、部分和键的名称,但是我不确定如何获取模板函数试图转换为的类型的名称。
My current code looks like, with specialisations for just plain strings and such:
我当前的代码看起来像,专门针对普通字符串等:
template<typename T> T GetValue(const std::wstring §ion, 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 rather not have to make specific overloads for every type that the data files might use, since there are loads of them...
我宁愿不必为数据文件可能使用的每种类型进行特定的重载,因为它们有很多……
Also I need a solution that does not incur any runtime overhead unless an exception occurs, i.e. a completely compile time solution is what I want since this code is called tons of times and load times are already getting somewhat long.
此外,我需要一个解决方案,除非发生异常,否则不会产生任何运行时开销,即完全编译时解决方案是我想要的,因为这段代码被调用了很多次并且加载时间已经变得有些长。
EDIT: Ok this is the solution I came up with:
编辑:好的,这是我想出的解决方案:
I have a types.h containg the following
我有一个包含以下内容的 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;}
Then I can use the DEFINE_TYPE_NAME macro to in cpp files for each type I need to deal with (eg in the cpp file that defined the type to start with).
然后我可以在 cpp 文件中使用 DEFINE_TYPE_NAME 宏来处理我需要处理的每种类型(例如,在定义开始的类型的 cpp 文件中)。
The linker is then able to find the appropirate template specialisation as long as it was defined somewhere, or throw a linker error otherwise so that I can add the type.
然后链接器能够找到合适的模板特化,只要它在某处定义,或者抛出链接器错误,以便我可以添加类型。
采纳答案by Logan Capaldo
Jesse Beder's solution is likely the best, but if you don't like the names typeid gives you (I think gcc gives you mangled names for instance), you can do something like:
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...
And then use it like
然后像这样使用它
throw ParseError(TypeParseTraits<T>::name);
EDIT:
编辑:
You could also combine the two, change name
to be a function that by default calls typeid(T).name()
and then only specialize for those cases where that's not acceptable.
您也可以将两者结合起来,更改name
为默认调用的函数,typeid(T).name()
然后仅针对那些不可接受的情况进行专门处理。
回答by Jesse Beder
回答by Bunkar
typeid(T).name()
is implementation defined and doesn't guarantee human readable string.
typeid(T).name()
是实现定义的,不保证人类可读的字符串。
Reading cppreference.com:
Returns an implementation defined null-terminated character string containing the name of the type. No guarantees are given, in particular, the returned string can be identical for several types and change between invocations of the same program.
...
With compilers such as gcc and clang, the returned string can be piped through c++filt -t to be converted to human-readable form.
返回包含类型名称的实现定义的以空字符结尾的字符串。不提供任何保证,特别是,返回的字符串对于多种类型可以是相同的,并且在同一程序的调用之间会发生变化。
...
使用 gcc 和 clang 等编译器,返回的字符串可以通过 c++filt -t 进行管道传输以转换为人类可读的形式。
But in some cases gcc doesn't return right string. For example on my machine I have gcc whith -std=c++11
and inside template function typeid(T).name()
returns "j"
for "unsigned int"
. It's so called mangled name. To get real type name, use
abi::__cxa_demangle()function (gcc only):
但在某些情况下,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;
}
回答by Andrey
As mentioned by Bunkar typeid(T).name is implementation defined.
正如 Bunkar 所提到的 typeid(T).name 是实现定义的。
To avoid this issue you can use Boost.TypeIndexlibrary.
为了避免这个问题,你可以使用Boost.TypeIndex库。
For example:
例如:
boost::typeindex::type_id<T>().pretty_name() // human readable
回答by rhomu
The answer of Logan Capaldo is correct but can be marginally simplified because it is unnecessary to specialize the class every time. One can write:
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...
This also allows you to put the REGISTER_PARSE_TYPE instructions in a C++ file...
这也允许您将 REGISTER_PARSE_TYPE 指令放在 C++ 文件中...
回答by chrisb2244
As a rephrasing of Andrey's answer:
作为安德烈回答的改写:
The Boost TypeIndexlibrary can be used to print names of types.
该升压TypeIndex库可用于打印的类型名称。
Inside a template, this might read as follows
在模板中,这可能如下所示
#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;
}
回答by HolyBlackCat
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.
所有主要编译器都支持__PRETTY_FUNC__
(GCC & Clang) / __FUNCSIG__
(MSVC) 作为扩展。
When used in a template like this:
当在这样的模板中使用时:
template <typename T> const char *foo()
{
#ifdef _MSC_VER
return __FUNCSIG__;
#else
return __PRETTY_FUNCTION__;
#endif
}
It produces strings in a compiler-dependent format, that contain, among other things, the name of T
.
它以依赖于编译器的格式生成字符串,其中包含T
.
E.g. foo<float>()
returns:
例如foo<float>()
返回:
"const char* foo() [with T = float]"
on GCC"const char *foo() [T = float]"
on Clang"const char *__cdecl foo<float>(void)"
on MSVC
"const char* foo() [with T = float]"
海湾合作委员会"const char *foo() [T = float]"
铿锵锵锵"const char *__cdecl foo<float>(void)"
在 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
.
不同编译器之间的结果名称可能略有不同。例如,GCC 省略了默认模板参数,并且 MSVC 使用单词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';
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>();
}
回答by Xar
I just leave it there. If someone will still need it, then you can use this:
我只是把它留在那里。如果有人仍然需要它,那么您可以使用它:
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
.
.
.
This will only CHECK type not GET it and only for 1 type or 2.
这只会检查类型而不是获取它,并且仅适用于 1 种类型或 2 种类型。
回答by Voyager
If you'd like a pretty_name, Logan Capaldo's solution can't deal with complex data structure: REGISTER_PARSE_TYPE(map<int,int>)
and typeid(map<int,int>).name()
gives me a result of St3mapIiiSt4lessIiESaISt4pairIKiiEEE
如果你想要一个漂亮的名字,Logan Capaldo 的解决方案无法处理复杂的数据结构:REGISTER_PARSE_TYPE(map<int,int>)
并typeid(map<int,int>).name()
给我一个结果St3mapIiiSt4lessIiESaISt4pairIKiiEEE
There is another interesting answer using unordered_map
or map
comes from https://en.cppreference.com/w/cpp/types/type_index.
还有另一个有趣的答案使用unordered_map
或map
来自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;
}
回答by jpo38
typeid(uint8_t).name()
is nice, but it returns "unsigned char" while you may expect "uint8_t".
typeid(uint8_t).name()
很好,但它返回“无符号字符”,而您可能期望“uint8_t”。
This piece of code will return you the appropriate type
这段代码将返回适当的类型
#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;
}