获取模板中类型的名称

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

解析[ MySectiom ] Key 的 example.txt. Value (“ notanwhole”)时出错,不是有效的 int

我可以从传递给模板函数和类中的成员 vars 的参数中计算出文件、节和键名,但是我不确定如何获得模板函数试图转换成的类型的名称。

我当前的代码看起来像是,专门用于纯字符串之类的:

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

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

然后,我可以在 cpp 文件中使用 DEFINE _ TYPE _ NAME 宏来处理我需要处理的每个类型(例如,在定义开始处理的类型的 cpp 文件中)。

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

173234 次浏览

The solution is

typeid(T).name()

where typeid(T) returns std::type_info.

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:

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.

typeid(T).name() is implementation defined and doesn't guarantee human readable string.

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.

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):

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

As mentioned by Bunkar typeid(T).name is implementation defined.

To avoid this issue you can use Boost.TypeIndex library.

For example:

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

As a rephrasing of Andrey's answer:

The Boost TypeIndex library can be used to print names of types.

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

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:

// 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...

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.

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

There is another interesting answer using unordered_map or map comes from 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;
}

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
}

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<decltype(1.2f)>(); << '\n';

Implementation: (uses C++20, but can be backported; see the edit history for a C++17 version)

#include <algorithm>
#include <array>
#include <cstddef>
#include <string_view>


namespace impl
{
template <typename T>
[[nodiscard]] constexpr std::string_view RawTypeName()
{
#ifndef _MSC_VER
return __PRETTY_FUNCTION__;
#else
return __FUNCSIG__;
#endif
}


struct TypeNameFormat
{
std::size_t junk_leading = 0;
std::size_t junk_total = 0;
};


constexpr TypeNameFormat type_name_format = []{
TypeNameFormat ret;
std::string_view sample = RawTypeName<int>();
ret.junk_leading = sample.find("int");
ret.junk_total = sample.size() - 3;
return ret;
}();
static_assert(type_name_format.junk_leading != std::size_t(-1), "Unable to determine the type name format on this compiler.");


template <typename T>
static constexpr auto type_name_storage = []{
std::array<char, RawTypeName<T>().size() - type_name_format.junk_total + 1> ret{};
std::copy_n(RawTypeName<T>().data() + type_name_format.junk_leading, ret.size() - 1, ret.data());
return ret;
}();
}


template <typename T>
[[nodiscard]] constexpr std::string_view TypeName()
{
return {impl::type_name_storage<T>.data(), impl::type_name_storage<T>.size() - 1};
}


template <typename T>
[[nodiscard]] constexpr const char *TypeNameCstr()
{
return impl::type_name_storage<T>.data();
}

typeid(uint8_t).name() is nice, but it returns "unsigned char" while you may expect "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;
}

There're many good answers here, but I'd guess the easiest is to use a library, isn't it? You can use this tiny lib, it's pretty cross-platform (tested with recent GCC/Clang/MSVC/ICC), supports C++11 onwards, works at compile time (consteval since C++20) and in theory it's going to support every compiler since the C++20's <source_location> gains wider support. A few more notes are in the README :)

I found this trick to publish a service interface with type information

#include <iostream>


using namespace std;
const char* nameOfType(int& ){ return "int";}
const char* nameOfType(const char* &){ return "string";}
const char* nameOfType(float& ){ return "float";}
const char* nameOfType(double& ){ return "double";}


template <typename T>
const char* templateFunction(T t){
return nameOfType(t)  ;
};


int main()
{
cout<<"Hello World this is an ";
cout << templateFunction<int>(1) << std::endl;
cout << templateFunction<const char*>("") << std::endl;
cout << templateFunction<double>(3.14) <<std::endl;
return 0;
}

Hope it can help somebody.

Complementing the awesome answer by @HolyBlackCat which does the job at compile time. I've managed to encapsulate the logic inside a struct so there's no need for a "impl" namespace, instead the 'impl' functions are guarded in a private manner.

template<typename T>
struct TypeInfo
{
private:
struct RawTypeNameFormat
{
std::size_t leading_junk = 0;
std::size_t trailing_junk = 0;
};


template<typename U>
static constexpr const auto& RawTypeName()
{
#ifdef _MSC_VER
return __FUNCSIG__;
#else
return __PRETTY_FUNCTION__;
#endif
}


// Returns `false` on failure.
static constexpr inline 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;
}


static constexpr inline 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).
[[nodiscard]]
static constexpr auto GetTypeName()
{
constexpr std::size_t len = sizeof(RawTypeName<T>()) - format.leading_junk - format.trailing_junk;
std::array<char, len> name{};
for (std::size_t i = 0; i < len - 1; i++)
name[i] = RawTypeName<T>()[i + format.leading_junk];
return name;
}


public:
[[nodiscard]]
static cstring Name()
{
static constexpr auto name = GetTypeName();
return name.data();
}


[[nodiscard]]
static cstring Name(const T&)
{
return name();
}
}

Example usage:

auto myTypeName = TypeInfo<MyType>::Name();

or:

const char* myTypeName = TypeInfo<MyType>::Name();

Since c++20 we can use std::source_location::function_name() to get a string containing the function name and arguments.

template<typename T>
consteval auto type_name()
{
std::string_view func_name(std::source_location::current().function_name()); // returns something like: consteval auto type_name() [with T = int]


auto extracted_params = ... Do some post processing here to extract the parameter names.
return extracted_params;
}

N.B.: at the time of writing (Oct 2022) MSVC does not report the template parameters, so this solution will not work there. Unfortunately, the form of the return value of function_name() isn't specified in the standard, but we may at least hope that they will add the template parameters in later versions.

Example