我正在编写一些用于解析某些文本数据文件的模板类,因此很可能绝大多数解析错误都是由于数据文件中的错误造成的,这些错误大部分都不是由程序员编写的,所以需要一个关于为什么app无法加载的好消息,例如类似的东西:
解析example.txt时出错。 [MySectiom] Key的值(“notaninteger”)不是有效的int
我可以从传递给模板函数的参数和类中的成员变量中找出文件,节和键名,但是我不知道如何获取模板函数试图转换的类型的名称到。
我当前的代码看起来很像,只有普通字符串的特殊化:
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而不是必须为数据文件可能使用的每种类型进行特定的重载,因为它们有很多...
此外,我需要一个不会产生任何运行时开销的解决方案,除非发生异常,即完全编译时解决方案是我想要的,因为此代码被称为大量时间并且加载时间已经变得有点长。
编辑:好的,这是我提出的解决方案:
我有一个包含以下
的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文件中)。
然后链接器能够找到适当的模板特化,只要它在某处定义,否则抛出链接器错误,以便我可以添加类型。
答案 0 :(得分:58)
答案 1 :(得分:39)
typeid(T).name()
是实现定义的,不保证人类可读的字符串。
返回一个实现定义的以null结尾的字符串 包含类型的名称。不给予任何保证 特别是,返回的字符串对于几种类型可以是相同的 在同一程序的调用之间进行更改。
...
使用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;
}
答案 2 :(得分: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...
然后像
一样使用它throw ParseError(TypeParseTraits<T>::name);
编辑:
您也可以将两者结合起来,将name
更改为默认情况下调用typeid(T).name()
的函数,然后仅专门针对那些不可接受的情况。
答案 3 :(得分:18)
正如Bunkar提到的,typeid(T).name是实现定义的。
要避免此问题,您可以使用Boost.TypeIndex库。
例如:
boost::typeindex::type_id<T>().pretty_name() // human readable
答案 4 :(得分:9)
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 ++文件中......
答案 5 :(得分: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;
}
答案 6 :(得分: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;
}
答案 7 :(得分: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
.
.
.
这只会检查类型而不是GET,只适用于1种或2种。
答案 8 :(得分:0)
如果您要使用pretty_name,Logan Capaldo的解决方案将无法处理复杂的数据结构:REGISTER_PARSE_TYPE(map<int,int>)
而typeid(map<int,int>).name()
给了我St3mapIiiSt4lessIiESaISt4pairIKiiEEE
使用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;
}
答案 9 :(得分:0)
这个技巧在其他几个问题中被提及,但在这里还没有。
所有主要编译器均支持__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]"
在GCC上"const char *foo() [T = float]"
on Clang "const char *__cdecl foo<float>(void)"
on 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>();
}