Подтвердить что ты не робот

С++ Получить имя типа в шаблоне

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

Анализ ошибок в примере .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));
    }
}

Идентификатор скорее не должен делать конкретные перегрузки для каждого типа, который могут использовать файлы данных, так как есть множество из них...

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

EDIT: Хорошо, это решение, с которым я столкнулся:

У меня есть типы .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, который определяет тип, который нужно начинать).

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

4b9b3361

Ответ 1

Решение Джесси Бедер, скорее всего, самое лучшее, но если вам не нравятся имена 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(), а затем специализируется только на тех случаях, когда это неприемлемо.

Ответ 2

Решение во время выполнения

typeid(T).name()

который возвращает (зависимое от компилятора, верю) имя типа, о котором идет речь. Он не будет вызываться, если исключение не будет выбрано (в вашем коде), поэтому оно может удовлетворить ваши критерии.

Ответ 3

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

Чтение cppreference.com:

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

...

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

Ответ 4

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

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

Например:

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

Ответ 5

Как перефразирование ответа Андрея:

Библиотека 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

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

// 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 в файле С++...