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

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

Ошибка разбора 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, который определил тип для начала).

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

14 ответов

Решение

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

Решение

typeid(T).name()

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

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

Чтение cppreference.com:

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

...

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

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

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

Например:

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

Этот трюк упоминался в нескольких других вопросах, но пока не здесь.

Поддержка всех основных компиляторов __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]" на Clang
  • "const char *__cdecl foo<float>(void)" на 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>();
}

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

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

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

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

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

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

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

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 type или 2.

Начиная с С++20 мы можем использоватьstd::source_location::function_name()чтобы получить строку, содержащую имя функции и аргументы.

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

NB: на момент написания (октябрь 2022 г.) MSVC не сообщает параметры шаблона, поэтому это решение не будет работать там. К сожалению, форма возвращаемого значенияfunction_name()не указан в стандарте, но мы можем, по крайней мере, надеяться, что они добавят параметры шаблона в более поздних версиях.

Пример

Я нашел этот трюк, чтобы опубликовать интерфейс службы с информацией о типе

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

Надеюсь, это может помочь кому-то.

Дополняя потрясающий ответ @HolyBlackCat, который выполняет работу во время компиляции. Мне удалось инкапсулировать логику внутри структуры, поэтому нет необходимости в пространстве имен «impl», вместо этого функции «impl» защищены вprivateспособ.

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

Пример использования:

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

или:

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

Здесь есть много хороших ответов, но я думаю, проще всего использовать библиотеку, не так ли? Вы можете использовать эту крошечную библиотеку , она довольно кроссплатформенная (протестирована с недавними GCC/Clang/MSVC/ICC), поддерживает C++11 и далее, работает во время компиляции (consteval начиная с C++20) и теоретически она собирается поддерживают все компиляторы, поскольку <source_location> C++20 получает более широкую поддержку. Еще несколько замечаний в README :)

Другие вопросы по тегам