C ++ Получить имя типа в шаблоне - PullRequest
67 голосов
/ 28 июня 2009

Я пишу некоторые классы шаблонов для анализа некоторых файлов текстовых данных, и поэтому большинство ошибок синтаксического анализа, вероятно, будет связано с ошибками в файле данных, которые по большей части не написаны программистами, и поэтому нужно хорошее сообщение о том, почему приложение не удалось загрузить, например, что-то вроде:

Ошибка разбора example.txt. Значение ("notaninteger") ключа [MySectiom] не является допустимым 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;}

Затем я могу использовать макрос DEFINE_TYPE_NAME для в файлах cpp для каждого типа, с которым мне нужно иметь дело (например, в файле cpp, который определил тип для начала).

Затем компоновщик может найти специализацию подходящего шаблона, если он где-то был определен, или выдать ошибку компоновщика, чтобы я мог добавить тип.

Ответы [ 8 ]

59 голосов
/ 28 июня 2009

Решение

typeid(T).name()

, который возвращает std :: type_info .

39 голосов
/ 01 октября 2013

typeid(T).name() определяется реализацией и не гарантирует удобочитаемую строку.

Чтение cppreference.com :

Возвращает определенную реализацией строку символов с нулевым символом в конце содержащий имя типа. Нет гарантий, в В частности, возвращаемая строка может быть идентичной для нескольких типов и переключаться между вызовами одной и той же программы.

...

С такими компиляторами, как gcc и clang, возвращаемая строка может быть передана через c ++ filter -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;
}
35 голосов
/ 28 июня 2009

Решение Джесси Бедера, вероятно, лучшее, но если вам не нравятся имена, которые дает вам 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);

EDIT:

Вы также можете объединить два, изменить name на функцию, которая по умолчанию вызывает typeid(T).name(), а затем специализировать только для тех случаев, когда это неприемлемо.

18 голосов
/ 26 февраля 2015

Как уже упоминалось, Bunkar typeid (T) .name определяется реализацией.

Чтобы избежать этой проблемы, вы можете использовать библиотеку Boost.TypeIndex .

Например:

boost::typeindex::type_id<T>().pretty_name() // human readable
9 голосов
/ 29 апреля 2016

Ответ Логана Капальдо правильный, но его можно немного упростить, поскольку нет необходимости каждый раз специализировать класс. Можно написать:

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

8 голосов
/ 26 октября 2015

Перефразируя ответ Андрея:

Библиотека 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;
}
1 голос
/ 02 июня 2018

Я просто оставляю это там. Если кому-то это все еще нужно, вы можете использовать это:

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

Это будет только CHECK type, но не GET, а только для 1 типа или 2.

0 голосов
/ 22 апреля 2019

Если вам нужно симпатичное имя, решение Логана Капальдо не может справиться со сложной структурой данных: 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;
}
...