Avatar ·

Можно ли вывести имя типа переменной в стандартном C++?

Всем привет! Я только начал изучать C++ и столкнулся с проблемой. Мне нужно отлаживать код, и часто хочется узнать, какой именно тип имеет переменная в конкретный момент времени. В PHP или JavaScript я привык использовать var_dump() или typeof, которые показывают тип. В Python есть type(). А как это сделать в C++? Я попробовал использовать typeid из , но он выводит что-то странное вроде "i" для int или "PKc" для const char*. Например: ```cpp #include #include int main() { int a = 5; const char* str = "hello"; auto x = a + 3.14; std::cout << typeid(a).name() << std::endl; // выводит "i" std::cout << typeid(str).name() << std::endl; // выводит "PKc" std::cout << typeid(x).name() << std::endl; // выводит "d" return 0; } ``` Почему typeid().name() возвращает какие-то однобуквенные сокращения? Как получить нормальные человекочитаемые имена, например "int", "const char*", "double"? Я слышал, что есть какой-то трюк с макросами или шаблонами, но не могу найти понятного объяснения. Помогите, пожалуйста, разобраться. Заранее спасибо! <<>>

Проблема typeid().name() и её решение

Действительно, typeid().name() возвращает "искажённые" (mangled) имена типов. Это внутреннее представление компилятора, которое не предназначено для чтения человеком. В разных компиляторах эти имена выглядят по-разному: MSVC может показать "int", а GCC/Clang — "i". К счастью, есть несколько способов получить нормальные имена.

Способ 1: Использование макроса и шаблона (рекомендуется)

Лучший кроссплатформенный метод — написать простую функцию-шаблон, которая использует __PRETTY_FUNCTION__ (GCC/Clang) или __FUNCSIG__ (MSVC). Эти макросы содержат полную сигнатуру функции, включая имя типа.

#include <iostream>
#include <string>
#include <type_traits>

// Функция для получения имени типа в читаемом виде
template <typename T>
constexpr std::string_view type_name() {
#if defined(__clang__) || defined(__GNUC__)
    // Для GCC и Clang
    return __PRETTY_FUNCTION__;
#elif defined(_MSC_VER)
    // Для MSVC
    return __FUNCSIG__;
#endif
}

// Утилита для извлечения чистого имени
template <typename T>
std::string type_name_clean() {
    std::string_view raw = type_name<T>();
    
    // Ищем позицию имени типа в строке сигнатуры
    auto start = raw.find('=');
    if (start == std::string_view::npos) start = 0;
    else start += 2; // пропускаем "= "
    
    auto end = raw.find(';', start);
    if (end == std::string_view::npos) end = raw.find(']', start);
    if (end == std::string_view::npos) end = raw.size() - 1;
    
    return std::string(raw.substr(start, end - start));
}

int main() {
    int a = 5;
    const char* str = "hello";
    auto x = a + 3.14;
    
    std::cout << type_name_clean<decltype(a)>() << std::endl;   // "int"
    std::cout << type_name_clean<decltype(str)>() << std::endl; // "const char*"
    std::cout << type_name_clean<decltype(x)>() << std::endl;  // "double"
    
    return 0;
}

Способ 2: Использование Boost.TypeIndex

Если вы используете Boost, есть готовое решение:

#include <boost/type_index.hpp>
#include <iostream>

int main() {
    int a = 5;
    auto b = a + 3.14f;
    std::cout << boost::typeindex::type_id<decltype(a)>().pretty_name() << std::endl; // "int"
    std::cout << boost::typeindex::type_id<decltype(b)>().pretty_name() << std::endl; // "float"
    return 0;
}

Как работает первый способ?

  • __PRETTY_FUNCTION__ — в GCC/Clang содержит полную сигнатуру функции, например: constexpr std::string_view type_name() [with T = int; ...]
  • __FUNCSIG__ — в MSVC делает то же самое: class std::basic_string_view<char,struct std::char_traits<char> > __cdecl type_name<int>(void)
  • Мы извлекаем имя типа, находя часть после "T = " (или аналогичного разделителя).

Важно!

В C++23 появился оператор std::is_same_v и улучшенный std::source_location, но для получения строкового представления типа всё ещё нет стандартного способа. Начиная с C++26, возможно, добавится std::type_info::name() с читаемыми именами, но пока приходится использовать трюки с макросами.

Для простого отладочного вывода можно использовать более короткую версию:

#define TYPE_NAME(x) []() { \
    std::cout << #x << " is " << type_name_clean<decltype(x)>() << std::endl; \
}()

int main() {
    int a = 5;
    TYPE_NAME(a); // выведет: "a is int"
}
<<>>

Альтернативный подход: ручное декодирование typeid().name()

Если вы не хотите использовать макросы или Boost, можно написать функцию для декодирования "искажённых" имён. Это работает только для GCC/Clang, так как MSVC использует другую схему:

#include <iostream>
#include <typeinfo>
#include <cxxabi.h> // только для GCC/Clang

std::string demangle(const char* mangled_name) {
    int status = 0;
    char* demangled = abi::__cxa_demangle(mangled_name, nullptr, nullptr, &status);
    std::string result = (status == 0) ? demangled : mangled_name;
    std::free(demangled);
    return result;
}

int main() {
    auto x = 42.5f;
    std::cout << demangle(typeid(x).name()) << std::endl; // "float"
    return 0;
}

Недостатки этого метода

  • Работает только на GCC/Clang (требуется cxxabi.h).
  • Не подходит для MSVC (там нужно использовать UnDecorateSymbolName из Windows API).
  • Медленнее, чем использование макросов, так как требует динамической аллокации.

Для простых проектов этот способ сойдёт, но для серьёзной разработки лучше использовать первый вариант из основного ответа.