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

Например:

int a = 12;
cout << typeof(a) << endl;

Ожидаемый результат:

int

Вот краткое изложение длинного решения Говарда, но реализованного с помощью еретического однострочного макроса: #define DEMANGLE_TYPEID_NAME(x) abi::__cxa_demangle(typeid((x)).name(), NULL, NULL, NULL). Если вам нужна кроссплатформенная поддержка: используйте #ifdef, #else, #endif, чтобы предоставить один макрос для других платформ, таких как MSVC.

Trevor Boyd Smith 05.07.2016 18:01

С более явным требованием для чтения человеком: stackoverflow.com/questions/12877521/…

Ciro Santilli TRUMP BAN IS BAD 23.07.2016 01:05

Если вы используете это только для отладки, вы можете рассмотреть template<typename T> void print_T() { std::cout << __PRETTY_FUNCTION__ << '\n'; }. Затем, используя, например, print_T<const int * const **>(); напечатает void print_T() [T = const int *const **] во время выполнения и сохранит все квалификаторы (работает в GCC и Clang).

Henri Menke 29.05.2017 09:01

@Henri, __PRETTY_FUNCTION__ не является стандартным C++ (требование указано в заголовке вопроса).

Toby Speight 13.02.2020 12:44
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
471
4
419 131
21
Перейти к ответу Данный вопрос помечен как решенный

Ответы 21

Пытаться:

#include <typeinfo>

// …
std::cout << typeid(a).name() << '\n';

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

Почему строка, возвращаемая функцией name (), определяется реализацией?

Destructor 02.09.2015 15:41

@PravasiMeet Насколько я знаю, нет никаких оснований. Комитет просто не хотел принуждать разработчиков компилятора к определенным техническим направлениям - возможно, это было ошибкой, если задним числом.

Konrad Rudolph 02.09.2015 17:52

Есть ли флаг, который я мог бы использовать для включения RTTI? Может быть, вы могли бы дать исчерпывающий ответ.

Jim 13.01.2016 19:22

@Destructor Предоставление стандартизированного формата изменения имен может создать впечатление, что взаимодействие между двоичными файлами, созданными двумя разными компиляторами, возможно и / или безопасно, когда это не так. Поскольку C++ не имеет стандартного ABI, стандартная схема изменения имен была бы бессмысленной, потенциально вводящей в заблуждение и опасной.

Elkvis 05.05.2016 18:52

@Jim Раздел о флагах компилятора был бы на порядок длиннее, чем сам ответ. GCC компилируется с включенным по умолчанию, поэтому "-fno-rtti", другие компиляторы могут этого не делать, но нет стандарта для флагов компилятора.

kfsone 04.10.2016 01:42

Не забудьте включить <typeinfo>

Я считаю, что вы имеете в виду идентификацию типа среды выполнения. Вы можете добиться этого, выполнив.

#include <iostream>
#include <typeinfo>

using namespace std;

int main() {
  int i;
  cout << typeid(i).name();
  return 0;
}

Для этого вы можете использовать класс черт. Что-то вроде:

#include <iostream>
using namespace std;

template <typename T> class type_name {
public:
    static const char *name;
};

#define DECLARE_TYPE_NAME(x) template<> const char *type_name<x>::name = #x;
#define GET_TYPE_NAME(x) (type_name<typeof(x)>::name)

DECLARE_TYPE_NAME(int);

int main()
{
    int a = 12;
    cout << GET_TYPE_NAME(a) << endl;
}

Определение DECLARE_TYPE_NAME существует для того, чтобы упростить вам жизнь при объявлении этого класса свойств для всех типов, которые, как вы ожидаете, могут понадобиться.

Это может быть более полезным, чем решения с typeid, потому что вы можете управлять выводом. Например, использование typeid для long long в моем компиляторе дает «x».

Вы можете использовать шаблоны.

template <typename T> const char* typeof(T&) { return "unknown"; }    // default
template<> const char* typeof(int&) { return "int"; }
template<> const char* typeof(float&) { return "float"; }

В приведенном выше примере, когда тип не совпадает, будет напечатано «unknown».

Не будет ли он печатать int для шорт и символов? А «плавать» для двоек?

gartenriese 17.02.2014 12:00

Специализация @gartenriese лишена этого недостатка. Для double он скомпилирует неспециализированную версию функции шаблона, а не сделает неявное преобразование типа для использования специализации: cpp.sh/2wzc

chappjc 04.03.2015 21:25

@chappjc: Честно говоря, я не знаю, почему я спросил об этом тогда, теперь мне все ясно. Но в любом случае спасибо за ответ на вопрос годичной давности!

gartenriese 05.03.2015 11:25

@gartenriese Я так и думал, но в какой-то момент в Интернете может возникнуть такой же вопрос.

chappjc 05.03.2015 11:28

Обратите внимание, что имена, сгенерированные функцией RTTI в C++, являются переносимыми нет. Например, класс

MyNamespace::CMyContainer<int, test_MyNamespace::CMyObject>

будут иметь следующие имена:

// MSVC 2003:
class MyNamespace::CMyContainer[int,class test_MyNamespace::CMyObject]
// G++ 4.2:
N8MyNamespace8CMyContainerIiN13test_MyNamespace9CMyObjectEEE

Таким образом, вы не можете использовать эту информацию для сериализации. Но, тем не менее, свойство typeid (a) .name () все еще можно использовать для целей журнала / отладки.

Другие ответы, связанные с RTTI (typeid), вероятно, то, что вы хотите, если:

  • вы можете позволить себе накладные расходы на память (которые могут быть значительными с некоторыми компиляторами)
  • имена классов, которые возвращает ваш компилятор, полезны

Альтернативой (аналогично ответу Грега Хьюджилла) является создание таблицы признаков во время компиляции.

template <typename T> struct type_as_string;

// declare your Wibble type (probably with definition of Wibble)
template <>
struct type_as_string<Wibble>
{
    static const char* const value = "Wibble";
};

Имейте в виду, что если вы заключите объявления в макрос, у вас возникнут проблемы с объявлением имен для типов шаблонов, принимающих более одного параметра (например, std :: map), из-за запятой.

Чтобы получить доступ к имени типа переменной, все, что вам нужно, это

template <typename T>
const char* get_type_as_string(const T&)
{
    return type_as_string<T>::value;
}

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

Greg Hewgill 18.09.2008 02:14

static const char * value = "Колебание"; ты не можешь этого сделать :)

Johannes Schaub - litb 01.12.2008 01:35

Очень уродливо, но помогает, если вам нужна только информация о времени компиляции (например, для отладки):

auto testVar = std::make_tuple(1, 1.0, "abc");
decltype(testVar)::foo= 1;

Возврат:

Compilation finished with errors:
source.cpp: In function 'int main()':
source.cpp:5:19: error: 'foo' is not a member of 'std::tuple<int, double, const char*>'

только C++ мог сделать это настолько трудным (печать типа автоматических переменных во время компиляции). ТОЛЬКО C++.

Karl Pickett 18.01.2017 23:04

@KarlP ну, честно говоря, это немного запутано, это тоже работает :) auto testVar = std::make_tuple(1, 1.0, "abc"); decltype(testVar)::foo = 1;

NickV 01.03.2017 16:59

В VC++ 17 это уменьшает ссылку на rvalue до простой ссылки, даже в функции шаблона с параметром ссылки пересылки и именем объекта, заключенным в std :: forward.

Jive Dadson 04.11.2017 20:45

Вы смогли добраться до этого типа, не создавая новых колес!

Steven Eckhoff 29.08.2018 18:08

Этот метод также описан в «Правиле 4: Умейте просматривать выведенные типы» в Effective Modern C++.

lenkite 07.06.2019 19:57

Как уже упоминалось, typeid().name() может возвращать искаженное имя. В GCC (и некоторых других компиляторах) вы можете обойти это с помощью следующего кода:

#include <cxxabi.h>
#include <iostream>
#include <typeinfo>
#include <cstdlib>

namespace some_namespace { namespace another_namespace {

  class my_class { };

} }

int main() {
  typedef some_namespace::another_namespace::my_class my_type;
  // mangled
  std::cout << typeid(my_type).name() << std::endl;

  // unmangled
  int status = 0;
  char* demangled = abi::__cxa_demangle(typeid(my_type).name(), 0, 0, &status);

  switch (status) {
    case -1: {
      // could not allocate memory
      std::cout << "Could not allocate memory" << std::endl;
      return -1;
    } break;
    case -2: {
      // invalid name under the C++ ABI mangling rules
      std::cout << "Invalid name" << std::endl;
      return -1;
    } break;
    case -3: {
      // invalid argument
      std::cout << "Invalid argument to demangle()" << std::endl;
      return -1;
    } break;
 }
 std::cout << demangled << std::endl;

 free(demangled);

 return 0;

}

Ответ принят как подходящий

Обновление C++ 11 до очень старого вопроса: тип переменной печати в C++.

Принятый (и хороший) ответ - использовать typeid(a).name(), где a - имя переменной.

Теперь в C++ 11 есть decltype(x), который может превращать выражение в тип. А decltype() имеет собственный набор очень интересных правил. Например, decltype(a) и decltype((a)), как правило, будут разных типов (и по уважительным и понятным причинам, как только эти причины будут выявлены).

Поможет ли нам наш верный typeid(a).name() исследовать этот дивный новый мир?

Нет.

Но инструмент, который будет, не так уж и сложен. И это тот инструмент, который я использую как ответ на этот вопрос. Я буду сравнивать этот новый инструмент с typeid(a).name(). И этот новый инструмент фактически построен на базе typeid(a).name().

Основной вопрос:

typeid(a).name()

отбрасывает cv-квалификаторы, ссылки и lvalue / rvalue-ness. Например:

const int ci = 0;
std::cout << typeid(ci).name() << '\n';

Для меня выходы:

i

и я предполагаю на выходах MSVC:

int

Т.е. const больше нет. Это не проблема QOI (качества реализации). Стандарт требует такого поведения.

Ниже я рекомендую:

template <typename T> std::string type_name();

который можно было бы использовать так:

const int ci = 0;
std::cout << type_name<decltype(ci)>() << '\n';

и для меня выходы:

int const

<disclaimer> Я не тестировал это на MSVC. </disclaimer> Но я приветствую отзывы тех, кто это делает.

Решение C++ 11

Я использую __cxa_demangle для платформ, отличных от MSVC, как рекомендует ипападоп в его ответе на типы разборки. Но на MSVC я доверяю typeid, чтобы разобрать имена (непроверенные). И это ядро ​​обернуто вокруг некоторого простого тестирования, которое обнаруживает, восстанавливает и сообщает cv-квалификаторы и ссылки на тип ввода.

#include <type_traits>
#include <typeinfo>
#ifndef _MSC_VER
#   include <cxxabi.h>
#endif
#include <memory>
#include <string>
#include <cstdlib>

template <class T>
std::string
type_name()
{
    typedef typename std::remove_reference<T>::type TR;
    std::unique_ptr<char, void(*)(void*)> own
           (
#ifndef _MSC_VER
                abi::__cxa_demangle(typeid(TR).name(), nullptr,
                                           nullptr, nullptr),
#else
                nullptr,
#endif
                std::free
           );
    std::string r = own != nullptr ? own.get() : typeid(TR).name();
    if (std::is_const<TR>::value)
        r += " const";
    if (std::is_volatile<TR>::value)
        r += " volatile";
    if (std::is_lvalue_reference<T>::value)
        r += "&";
    else if (std::is_rvalue_reference<T>::value)
        r += "&&";
    return r;
}

Результаты, достижения

С помощью этого решения я могу сделать это:

int& foo_lref();
int&& foo_rref();
int foo_value();

int
main()
{
    int i = 0;
    const int ci = 0;
    std::cout << "decltype(i) is " << type_name<decltype(i)>() << '\n';
    std::cout << "decltype((i)) is " << type_name<decltype((i))>() << '\n';
    std::cout << "decltype(ci) is " << type_name<decltype(ci)>() << '\n';
    std::cout << "decltype((ci)) is " << type_name<decltype((ci))>() << '\n';
    std::cout << "decltype(static_cast<int&>(i)) is " << type_name<decltype(static_cast<int&>(i))>() << '\n';
    std::cout << "decltype(static_cast<int&&>(i)) is " << type_name<decltype(static_cast<int&&>(i))>() << '\n';
    std::cout << "decltype(static_cast<int>(i)) is " << type_name<decltype(static_cast<int>(i))>() << '\n';
    std::cout << "decltype(foo_lref()) is " << type_name<decltype(foo_lref())>() << '\n';
    std::cout << "decltype(foo_rref()) is " << type_name<decltype(foo_rref())>() << '\n';
    std::cout << "decltype(foo_value()) is " << type_name<decltype(foo_value())>() << '\n';
}

и вывод:

decltype(i) is int
decltype((i)) is int&
decltype(ci) is int const
decltype((ci)) is int const&
decltype(static_cast<int&>(i)) is int&
decltype(static_cast<int&&>(i)) is int&&
decltype(static_cast<int>(i)) is int
decltype(foo_lref()) is int&
decltype(foo_rref()) is int&&
decltype(foo_value()) is int

Обратите внимание (например) на разницу между decltype(i) и decltype((i)). Первый - это тип декларацияi. Последний является «типом» выражениеi. (выражения никогда не имеют ссылочного типа, но по соглашению decltype представляет выражения lvalue со ссылками на lvalue).

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

Напротив, если бы я построил это только на typeid(a).name(), без добавления обратно потерянных cv-квалификаторов или ссылок, результат был бы следующим:

decltype(i) is int
decltype((i)) is int
decltype(ci) is int
decltype((ci)) is int
decltype(static_cast<int&>(i)) is int
decltype(static_cast<int&&>(i)) is int
decltype(static_cast<int>(i)) is int
decltype(foo_lref()) is int
decltype(foo_rref()) is int
decltype(foo_value()) is int

Т.е. Все ссылки и CV-квалификаторы удалены.

Обновление C++ 14

Когда вы думаете, что нашли решение проблемы, кто-то всегда появляется из ниоткуда и показывает вам гораздо лучший путь. :-)

Этот ответ из Джамбори показывает, как получить имя типа в C++ 14 во время компиляции. Это отличное решение по двум причинам:

  1. Это во время компиляции!
  2. Вы заставляете сам компилятор выполнять эту работу вместо библиотеки (даже std :: lib). Это означает более точные результаты для последних языковых функций (например, лямбды).

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

#include <cstddef>
#include <stdexcept>
#include <cstring>
#include <ostream>

#ifndef _MSC_VER
#  if __cplusplus < 201103
#    define CONSTEXPR11_TN
#    define CONSTEXPR14_TN
#    define NOEXCEPT_TN
#  elif __cplusplus < 201402
#    define CONSTEXPR11_TN constexpr
#    define CONSTEXPR14_TN
#    define NOEXCEPT_TN noexcept
#  else
#    define CONSTEXPR11_TN constexpr
#    define CONSTEXPR14_TN constexpr
#    define NOEXCEPT_TN noexcept
#  endif
#else  // _MSC_VER
#  if _MSC_VER < 1900
#    define CONSTEXPR11_TN
#    define CONSTEXPR14_TN
#    define NOEXCEPT_TN
#  elif _MSC_VER < 2000
#    define CONSTEXPR11_TN constexpr
#    define CONSTEXPR14_TN
#    define NOEXCEPT_TN noexcept
#  else
#    define CONSTEXPR11_TN constexpr
#    define CONSTEXPR14_TN constexpr
#    define NOEXCEPT_TN noexcept
#  endif
#endif  // _MSC_VER

class static_string
{
    const char* const p_;
    const std::size_t sz_;

public:
    typedef const char* const_iterator;

    template <std::size_t N>
    CONSTEXPR11_TN static_string(const char(&a)[N]) NOEXCEPT_TN
        : p_(a)
        , sz_(N-1)
        {}

    CONSTEXPR11_TN static_string(const char* p, std::size_t N) NOEXCEPT_TN
        : p_(p)
        , sz_(N)
        {}

    CONSTEXPR11_TN const char* data() const NOEXCEPT_TN {return p_;}
    CONSTEXPR11_TN std::size_t size() const NOEXCEPT_TN {return sz_;}

    CONSTEXPR11_TN const_iterator begin() const NOEXCEPT_TN {return p_;}
    CONSTEXPR11_TN const_iterator end()   const NOEXCEPT_TN {return p_ + sz_;}

    CONSTEXPR11_TN char operator[](std::size_t n) const
    {
        return n < sz_ ? p_[n] : throw std::out_of_range("static_string");
    }
};

inline
std::ostream&
operator<<(std::ostream& os, static_string const& s)
{
    return os.write(s.data(), s.size());
}

template <class T>
CONSTEXPR14_TN
static_string
type_name()
{
#ifdef __clang__
    static_string p = __PRETTY_FUNCTION__;
    return static_string(p.data() + 31, p.size() - 31 - 1);
#elif defined(__GNUC__)
    static_string p = __PRETTY_FUNCTION__;
#  if __cplusplus < 201402
    return static_string(p.data() + 36, p.size() - 36 - 1);
#  else
    return static_string(p.data() + 46, p.size() - 46 - 1);
#  endif
#elif defined(_MSC_VER)
    static_string p = __FUNCSIG__;
    return static_string(p.data() + 38, p.size() - 38 - 7);
#endif
}

Этот код автоматически откатится на constexpr, если вы все еще застряли на древнем C++ 11. И если вы рисуете на стене пещеры с помощью C++ 98/03, в жертву также приносится noexcept.

Обновление C++ 17

В комментариях ниже Lyberta указывает, что новый std::string_view может заменить static_string:

template <class T>
constexpr
std::string_view
type_name()
{
    using namespace std;
#ifdef __clang__
    string_view p = __PRETTY_FUNCTION__;
    return string_view(p.data() + 34, p.size() - 34 - 1);
#elif defined(__GNUC__)
    string_view p = __PRETTY_FUNCTION__;
#  if __cplusplus < 201402
    return string_view(p.data() + 36, p.size() - 36 - 1);
#  else
    return string_view(p.data() + 49, p.find(';', 49) - 49);
#  endif
#elif defined(_MSC_VER)
    string_view p = __FUNCSIG__;
    return string_view(p.data() + 84, p.size() - 84 - 7);
#endif
}

Я обновил константы для VS благодаря очень хорошей детективной работе Джайва Дадсона в комментариях ниже.

Обновлять:

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

VS 14 CTP распечатал правильные типы, мне нужно было добавить только одну строку #include <iostream>.

Max Galkin 21.12.2014 10:02

Почему template <typename T> std :: string type_name ()? Почему вы не передаете тип в качестве аргумента?

moonman239 31.12.2015 03:05

Я считаю, что мое объяснение состояло в том, что иногда у меня Только был тип (например, выведенный параметр шаблона), и я не хотел, чтобы мне приходилось искусственно конструировать один из них, чтобы получить тип (хотя в наши дни declval будет выполнять эту работу).

Howard Hinnant 31.12.2015 05:29

Извините за глупый и неубедительный вопрос, но будет ли это работать с типами-членами типов классов? Спасибо

Angelus Mortis 02.03.2016 21:14

@AngelusMortis: поскольку английский язык расплывчатый / двусмысленный по сравнению с кодом C++, я рекомендую вам скопировать / вставить это в свой тестовый пример с конкретным типом, который вас интересует, и с конкретным компилятором, который вас интересует, и напишите еще подробности, если результат неожиданный и / или неудовлетворительный.

Howard Hinnant 03.03.2016 01:20

@HowardHinnant можно ли использовать std::string_view вместо static_string?

user3624760 09.08.2017 23:16

@Lyberta: У меня работает! Хорошее наблюдение. Также похоже, что clang изменил вывод __PRETTY_FUNCTION__ с тех пор, как я это написал. Используйте 34 вместо 31 в __clang__.

Howard Hinnant 09.08.2017 23:40

@Howard - После изменения static_string на string_view магические числа неверны. В VC++ 17 измените 38 на 84 в обоих местах.

Jive Dadson 05.11.2017 02:09

@JiveDadson: Спасибо!

Howard Hinnant 05.11.2017 02:14

@Howard - Пожалуйста. Цифры для других компиляторов, вероятно, тоже разные. У меня нет возможности проверить их.

Jive Dadson 05.11.2017 02:22

Стоит отметить: магические числа в этой функции изменятся, если вы поместите функцию в пространство имен. Я переписал его, добавив копии выходной строки (литерала) и функцию constexpr length, чтобы не приходилось искать ошибки в заборе.

aghast 20.02.2018 08:02

Интересно, спасибо за обновление. Похоже, VS2017 typeid(TR).name() радикально отличается от предыдущих версий.

Howard Hinnant 11.08.2018 18:28

GCC-9 говорит: the value of '__PRETTY_FUNCTION__' is not usable in a constant expression Я не уверен, ошибка это или намерение

hutorny 07.10.2018 20:36

@hutorny Это интересно. Для меня это звучит как регресс.

Howard Hinnant 08.10.2018 02:14

@HowardHinnant - моя ошибка :). Я использовал return string_view(__PRETTY_FUNCTION__);, и он работал до v8.2. С GCC 9.0 constexpr string_view pretty = __PRETTY_FUNCTION__; return pretty; работает должным образом

hutorny 08.10.2018 09:49

Просто чтобы привлечь внимание всех к предупреждению __PRETTY_FUNCTION__ - компиляторы хранят всю строку, а не только подстроку - если вы посмотрите здесь пример: godbolt.org/z/peqQ9O или запустите его там: wandbox.org/nojs/gcc-head/permlink/vUd1rNLX04ClIRic, вы увидите оригинальные длинные строки. Чтобы сохранить только то, что необходимо, подстроки следует копировать во время компиляции. Возможное решение здесь: wandbox.org/nojs/gcc-head/permlink/wxvacbsEQUZRvUZ5

hutorny 08.10.2018 12:14
«Стандарт требует такого поведения». While technically true because you're talking about how the expression is handled, this statement could be deemed a little misleading because, speaking far more generally, the standard mandates ничего about the return value of name(). Food for thought.
Lightness Races in Orbit 02.11.2018 14:25

Человек, я искал источник, где я реализовал этот трюк type_name на почти целый год, я должен был знать, что это должен быть кто-то из StackOverflow. Позже я укажу вам в моей библиотеке, не говоря уже о том, чтобы вместо этого интегрировать ваше дополнение uing string_view.

Luis Machuca 16.12.2018 23:15

Лучше использовать decltype(std::free), пользовательский класс-объект-функцию или лямбду вместо void(*)(void*), поскольку тип точныйstd::free не обязательно должен быть void(*)(void*). std::integral_constant<decltype(std::free), std::free> тоже подойдет.

Deduplicator 25.12.2018 13:02

Магические числа подвержены изменениям. Функция stackoverflow.com/a/58331141/7163942 автоматически вычисляет префикс / суффикс и соответствующие магические числа.

Val 03.09.2020 14:54

Согласовано. stackoverflow.com/a/58331141/576911 - мой текущий фаворит (и получил одобрение).

Howard Hinnant 03.09.2020 16:50

HowardHinnant: Не могли бы вы взглянуть на еще один брать об избежании магических чисел?

einpoklum 23.10.2020 00:38

@einpoklum Очень красиво!

Howard Hinnant 23.10.2020 05:15

Привет, как продолжение, в версии C++ 11 простое добавление ссылочных меток может быть не на 100% правильным, например, int (&) [100] выведет что-то вроде int [100]&, более поздняя версия очень хороша!

SHP 11.03.2021 13:53

Мне нравится метод Ника. Полная форма может быть такой (для всех основных типов данных):

template <typename T> const char* typeof(T&) { return "unknown"; }    // default
template<> const char* typeof(int&) { return "int"; }
template<> const char* typeof(short&) { return "short"; }
template<> const char* typeof(long&) { return "long"; }
template<> const char* typeof(unsigned&) { return "unsigned"; }
template<> const char* typeof(unsigned short&) { return "unsigned short"; }
template<> const char* typeof(unsigned long&) { return "unsigned long"; }
template<> const char* typeof(float&) { return "float"; }
template<> const char* typeof(double&) { return "double"; }
template<> const char* typeof(long double&) { return "long double"; }
template<> const char* typeof(std::string&) { return "String"; }
template<> const char* typeof(char&) { return "char"; }
template<> const char* typeof(signed char&) { return "signed char"; }
template<> const char* typeof(unsigned char&) { return "unsigned char"; }
template<> const char* typeof(char*&) { return "char*"; }
template<> const char* typeof(signed char*&) { return "signed char*"; }
template<> const char* typeof(unsigned char*&) { return "unsigned char*"; }

(i) он не будет работать для других типов (т.е. совсем не универсальный); (ii) бесполезный раздувание кода; (iii) то же самое можно (правильно) сделать с typeid или decltype.

edmz 13.03.2015 22:57

Вы правы, но он охватывает все основные типы ... и это то, что мне сейчас нужно ...

Jahid 13.03.2015 23:08

Можете ли вы сказать мне, как бы вы это сделали с помощью decltype,

Jahid 13.03.2015 23:16

Если это тест во время компиляции, вы можете использовать std :: is_same <T, S> и decltype для получения T и S.

edmz 14.03.2015 10:11

Более общее решение без перегрузки функций, чем мое предыдущее:

template<typename T>
std::string TypeOf(T){
    std::string Type = "unknown";
    if (std::is_same<T,int>::value) Type = "int";
    if (std::is_same<T,std::string>::value) Type = "String";
    if (std::is_same<T,MyClass>::value) Type = "MyClass";

    return Type;}

Здесь MyClass - это класс, определенный пользователем. Здесь также можно добавить дополнительные условия.

Пример:

#include <iostream>



class MyClass{};


template<typename T>
std::string TypeOf(T){
    std::string Type = "unknown";
    if (std::is_same<T,int>::value) Type = "int";
    if (std::is_same<T,std::string>::value) Type = "String";
    if (std::is_same<T,MyClass>::value) Type = "MyClass";
    return Type;}


int main(){;
    int a=0;
    std::string s = "";
    MyClass my;
    std::cout<<TypeOf(a)<<std::endl;
    std::cout<<TypeOf(s)<<std::endl;
    std::cout<<TypeOf(my)<<std::endl;

    return 0;}

Выход:

int
String
MyClass

Вы также можете использовать фильтр C++ с опцией -t (тип), чтобы распознать имя типа:

#include <iostream>
#include <typeinfo>
#include <string>

using namespace std;

int main() {
  auto x = 1;
  string my_type = typeid(x).name();
  system(("echo " + my_type + " | c++filt -t").c_str());
  return 0;
}

Проверено только на linux.

Чертовски уродливо, но сделаю то, что мне нужно. И намного меньше, чем другие решения. Кстати, работает на Mac.

Marco Luglio 20.05.2016 03:40

В C++ 11 у нас есть decltype. В стандартном C++ нет возможности отобразить точный тип переменной, объявленной с помощью decltype. Мы можем использовать boost typeindex, то есть type_id_with_cvr (cvr означает const, volatile, reference) для печати типа, как показано ниже.

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

using namespace std;
using boost::typeindex::type_id_with_cvr;

int main() {
  int i = 0;
  const int ci = 0;
  cout << "decltype(i) is " << type_id_with_cvr<decltype(i)>().pretty_name() << '\n';
  cout << "decltype((i)) is " << type_id_with_cvr<decltype((i))>().pretty_name() << '\n';
  cout << "decltype(ci) is " << type_id_with_cvr<decltype(ci)>().pretty_name() << '\n';
  cout << "decltype((ci)) is " << type_id_with_cvr<decltype((ci))>().pretty_name() << '\n';
  cout << "decltype(std::move(i)) is " << type_id_with_cvr<decltype(std::move(i))>().pretty_name() << '\n';
  cout << "decltype(std::static_cast<int&&>(i)) is " << type_id_with_cvr<decltype(static_cast<int&&>(i))>().pretty_name() << '\n';
  return 0;
}

было бы проще использовать вспомогательную функцию: template<typename T> void print_type(T){cout << "type T is: "<< type_id_with_cvr<T>().pretty_name()<< '\n';}

r0ng 18.10.2017 04:47

#include <iostream>
#include <typeinfo>
using namespace std;
#define show_type_name(_t) \
    system(("echo " + string(typeid(_t).name()) + " | c++filt -t").c_str())

int main() {
    auto a = {"one", "two", "three"};
    cout << "Type of a: " << typeid(a).name() << endl;
    cout << "Real type of a:\n";
    show_type_name(a);
    for (auto s : a) {
        if (string(s) == "one") {
            cout << "Type of s: " << typeid(s).name() << endl;
            cout << "Real type of s:\n";
            show_type_name(s);
        }
        cout << s << endl;
    }

    int i = 5;
    cout << "Type of i: " << typeid(i).name() << endl;
    cout << "Real type of i:\n";
    show_type_name(i);
    return 0;
}

Выход:

Type of a: St16initializer_listIPKcE
Real type of a:
std::initializer_list<char const*>
Type of s: PKc
Real type of s:
char const*
one
two
three
Type of i: i
Real type of i:
int

Когда я бросил вызов, я решил проверить, насколько далеко можно зайти с помощью платформенно-независимого (надеюсь) трюка с шаблонами.

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

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

TYPE_NAME(int)
TYPE_NAME(void)
// You probably should list all primitive types here.

TYPE_NAME(std::string)

int main()
{
    // A simple case
    std::cout << type_name<void(*)(int)> << '\n';
    // -> `void (*)(int)`

    // Ugly mess case
    // Note that compiler removes cv-qualifiers from parameters and replaces arrays with pointers.
    std::cout << type_name<void (std::string::*(int[3],const int, void (*)(std::string)))(volatile int*const*)> << '\n';
    // -> `void (std::string::*(int *,int,void (*)(std::string)))(volatile int *const*)`

    // A case with undefined types
    //  If a type wasn't TYPE_NAME'd, it's replaced by a placeholder, one of `class?`, `union?`, `enum?` or `??`.
    std::cout << type_name<std::ostream (*)(int, short)> << '\n';
    // -> `class? (*)(int,??)`
    // With appropriate TYPE_NAME's, the output would be `std::string (*)(int,short)`.
}

Код:

#include <type_traits>
#include <utility>

static constexpr std::size_t max_str_lit_len = 256;

template <std::size_t I, std::size_t N> constexpr char sl_at(const char (&str)[N])
{
    if constexpr(I < N)
        return str[I];
    else
        return '\0';
}

constexpr std::size_t sl_len(const char *str)
{
    for (std::size_t i = 0; i < max_str_lit_len; i++)
        if (str[i] == '\0')
            return i;
    return 0;
}

template <char ...C> struct str_lit
{
    static constexpr char value[] {C..., '\0'};
    static constexpr int size = sl_len(value);

    template <typename F, typename ...P> struct concat_impl {using type = typename concat_impl<F>::type::template concat_impl<P...>::type;};
    template <char ...CC> struct concat_impl<str_lit<CC...>> {using type = str_lit<C..., CC...>;};
    template <typename ...P> using concat = typename concat_impl<P...>::type;
};

template <typename, const char *> struct trim_str_lit_impl;
template <std::size_t ...I, const char *S> struct trim_str_lit_impl<std::index_sequence<I...>, S>
{
    using type = str_lit<S[I]...>;
};
template <std::size_t N, const char *S> using trim_str_lit = typename trim_str_lit_impl<std::make_index_sequence<N>, S>::type;

#define STR_LIT(str) ::trim_str_lit<::sl_len(str), ::str_lit<STR_TO_VA(str)>::value>
#define STR_TO_VA(str) STR_TO_VA_16(str,0),STR_TO_VA_16(str,16),STR_TO_VA_16(str,32),STR_TO_VA_16(str,48)
#define STR_TO_VA_16(str,off) STR_TO_VA_4(str,0+off),STR_TO_VA_4(str,4+off),STR_TO_VA_4(str,8+off),STR_TO_VA_4(str,12+off)
#define STR_TO_VA_4(str,off) ::sl_at<off+0>(str),::sl_at<off+1>(str),::sl_at<off+2>(str),::sl_at<off+3>(str)

template <char ...C> constexpr str_lit<C...> make_str_lit(str_lit<C...>) {return {};}
template <std::size_t N> constexpr auto make_str_lit(const char (&str)[N])
{
    return trim_str_lit<sl_len((const char (&)[N])str), str>{};
}

template <std::size_t A, std::size_t B> struct cexpr_pow {static constexpr std::size_t value = A * cexpr_pow<A,B-1>::value;};
template <std::size_t A> struct cexpr_pow<A,0> {static constexpr std::size_t value = 1;};
template <std::size_t N, std::size_t X, typename = std::make_index_sequence<X>> struct num_to_str_lit_impl;
template <std::size_t N, std::size_t X, std::size_t ...Seq> struct num_to_str_lit_impl<N, X, std::index_sequence<Seq...>>
{
    static constexpr auto func()
    {
        if constexpr (N >= cexpr_pow<10,X>::value)
            return num_to_str_lit_impl<N, X+1>::func();
        else
            return str_lit<(N / cexpr_pow<10,X-1-Seq>::value % 10 + '0')...>{};
    }
};
template <std::size_t N> using num_to_str_lit = decltype(num_to_str_lit_impl<N,1>::func());


using spa = str_lit<' '>;
using lpa = str_lit<'('>;
using rpa = str_lit<')'>;
using lbr = str_lit<'['>;
using rbr = str_lit<']'>;
using ast = str_lit<'*'>;
using amp = str_lit<'&'>;
using con = str_lit<'c','o','n','s','t'>;
using vol = str_lit<'v','o','l','a','t','i','l','e'>;
using con_vol = con::concat<spa, vol>;
using nsp = str_lit<':',':'>;
using com = str_lit<','>;
using unk = str_lit<'?','?'>;

using c_cla = str_lit<'c','l','a','s','s','?'>;
using c_uni = str_lit<'u','n','i','o','n','?'>;
using c_enu = str_lit<'e','n','u','m','?'>;

template <typename T> inline constexpr bool ptr_or_ref = std::is_pointer_v<T> || std::is_reference_v<T> || std::is_member_pointer_v<T>;
template <typename T> inline constexpr bool func_or_arr = std::is_function_v<T> || std::is_array_v<T>;

template <typename T> struct primitive_type_name {using value = unk;};

template <typename T, typename = std::enable_if_t<std::is_class_v<T>>> using enable_if_class = T;
template <typename T, typename = std::enable_if_t<std::is_union_v<T>>> using enable_if_union = T;
template <typename T, typename = std::enable_if_t<std::is_enum_v <T>>> using enable_if_enum  = T;
template <typename T> struct primitive_type_name<enable_if_class<T>> {using value = c_cla;};
template <typename T> struct primitive_type_name<enable_if_union<T>> {using value = c_uni;};
template <typename T> struct primitive_type_name<enable_if_enum <T>> {using value = c_enu;};

template <typename T> struct type_name_impl;

template <typename T> using type_name_lit = std::conditional_t<std::is_same_v<typename primitive_type_name<T>::value::template concat<spa>,
                                                                               typename type_name_impl<T>::l::template concat<typename type_name_impl<T>::r>>,
                                            typename primitive_type_name<T>::value,
                                            typename type_name_impl<T>::l::template concat<typename type_name_impl<T>::r>>;
template <typename T> inline constexpr const char *type_name = type_name_lit<T>::value;

template <typename T, typename = std::enable_if_t<!std::is_const_v<T> && !std::is_volatile_v<T>>> using enable_if_no_cv = T;

template <typename T> struct type_name_impl
{
    using l = typename primitive_type_name<T>::value::template concat<spa>;
    using r = str_lit<>;
};
template <typename T> struct type_name_impl<const T>
{
    using new_T_l = std::conditional_t<type_name_impl<T>::l::size && !ptr_or_ref<T>,
                                       spa::concat<typename type_name_impl<T>::l>,
                                       typename type_name_impl<T>::l>;
    using l = std::conditional_t<ptr_or_ref<T>,
                                 typename new_T_l::template concat<con>,
                                 con::concat<new_T_l>>;
    using r = typename type_name_impl<T>::r;
};
template <typename T> struct type_name_impl<volatile T>
{
    using new_T_l = std::conditional_t<type_name_impl<T>::l::size && !ptr_or_ref<T>,
                                       spa::concat<typename type_name_impl<T>::l>,
                                       typename type_name_impl<T>::l>;
    using l = std::conditional_t<ptr_or_ref<T>,
                                 typename new_T_l::template concat<vol>,
                                 vol::concat<new_T_l>>;
    using r = typename type_name_impl<T>::r;
};
template <typename T> struct type_name_impl<const volatile T>
{
    using new_T_l = std::conditional_t<type_name_impl<T>::l::size && !ptr_or_ref<T>,
                                       spa::concat<typename type_name_impl<T>::l>,
                                       typename type_name_impl<T>::l>;
    using l = std::conditional_t<ptr_or_ref<T>,
                                 typename new_T_l::template concat<con_vol>,
                                 con_vol::concat<new_T_l>>;
    using r = typename type_name_impl<T>::r;
};
template <typename T> struct type_name_impl<T *>
{
    using l = std::conditional_t<func_or_arr<T>,
                                 typename type_name_impl<T>::l::template concat<lpa, ast>,
                                 typename type_name_impl<T>::l::template concat<     ast>>;
    using r = std::conditional_t<func_or_arr<T>,
                                 rpa::concat<typename type_name_impl<T>::r>,
                                             typename type_name_impl<T>::r>;
};
template <typename T> struct type_name_impl<T &>
{
    using l = std::conditional_t<func_or_arr<T>,
                                 typename type_name_impl<T>::l::template concat<lpa, amp>,
                                 typename type_name_impl<T>::l::template concat<     amp>>;
    using r = std::conditional_t<func_or_arr<T>,
                                 rpa::concat<typename type_name_impl<T>::r>,
                                             typename type_name_impl<T>::r>;
};
template <typename T> struct type_name_impl<T &&>
{
    using l = std::conditional_t<func_or_arr<T>,
                                 typename type_name_impl<T>::l::template concat<lpa, amp, amp>,
                                 typename type_name_impl<T>::l::template concat<     amp, amp>>;
    using r = std::conditional_t<func_or_arr<T>,
                                 rpa::concat<typename type_name_impl<T>::r>,
                                             typename type_name_impl<T>::r>;
};
template <typename T, typename C> struct type_name_impl<T C::*>
{
    using l = std::conditional_t<func_or_arr<T>,
                                 typename type_name_impl<T>::l::template concat<lpa, type_name_lit<C>, nsp, ast>,
                                 typename type_name_impl<T>::l::template concat<     type_name_lit<C>, nsp, ast>>;
    using r = std::conditional_t<func_or_arr<T>,
                                 rpa::concat<typename type_name_impl<T>::r>,
                                             typename type_name_impl<T>::r>;
};
template <typename T> struct type_name_impl<enable_if_no_cv<T[]>>
{
    using l = typename type_name_impl<T>::l;
    using r = lbr::concat<rbr, typename type_name_impl<T>::r>;
};
template <typename T, std::size_t N> struct type_name_impl<enable_if_no_cv<T[N]>>
{
    using l = typename type_name_impl<T>::l;
    using r = lbr::concat<num_to_str_lit<N>, rbr, typename type_name_impl<T>::r>;
};
template <typename T> struct type_name_impl<T()>
{
    using l = typename type_name_impl<T>::l;
    using r = lpa::concat<rpa, typename type_name_impl<T>::r>;
};
template <typename T, typename P1, typename ...P> struct type_name_impl<T(P1, P...)>
{
    using l = typename type_name_impl<T>::l;
    using r = lpa::concat<type_name_lit<P1>,
                          com::concat<type_name_lit<P>>..., rpa, typename type_name_impl<T>::r>;
};

#define TYPE_NAME(t) template <> struct primitive_type_name<t> {using value = STR_LIT(#t);};

Как объясняет Скотт Мейерс в книге «Эффективный современный C++»,

Calls to std::type_info::name are not guaranteed to return anythong sensible.

Лучшее решение - позволить компилятору генерировать сообщение об ошибке во время вывода типа, например,

template<typename T>
class TD;

int main(){
    const int theAnswer = 32;
    auto x = theAnswer;
    auto y = &theAnswer;
    TD<decltype(x)> xType;
    TD<decltype(y)> yType;
    return 0;
}

Результат будет примерно таким, в зависимости от компиляторов:

test4.cpp:10:21: error: aggregate ‘TD<int> xType’ has incomplete type and cannot be defined TD<decltype(x)> xType;

test4.cpp:11:21: error: aggregate ‘TD<const int *> yType’ has incomplete type and cannot be defined TD<decltype(y)> yType;

Следовательно, мы узнаем, что тип x - int, тип y - const int*.

Согласно решению Говард, если вам не нравится магическое число, я думаю, что это хороший способ представления, и он выглядит интуитивно понятным:

#include <string_view>

template <typename T>
constexpr auto type_name() noexcept {
  std::string_view name = "Error: unsupported compiler", prefix, suffix;
#ifdef __clang__
  name = __PRETTY_FUNCTION__;
  prefix = "auto type_name() [T = ";
  suffix = "]";
#elif defined(__GNUC__)
  name = __PRETTY_FUNCTION__;
  prefix = "constexpr auto type_name() [with T = ";
  suffix = "]";
#elif defined(_MSC_VER)
  name = __FUNCSIG__;
  prefix = "auto __cdecl type_name<";
  suffix = ">(void) noexcept";
#endif
  name.remove_prefix(prefix.size());
  name.remove_suffix(suffix.size());
  return name;
}

Демо.

Это отличное превращение усилий последних нескольких версий C++ в нечто короткое и приятное. +1.

einpoklum 27.12.2019 21:32

Это тоже моя любимая!

Howard Hinnant 04.01.2020 02:02

Вот аналогичная функция, которую я использую, которая автоматически определяет суффикс / префикс: stackoverflow.com/questions/1055452/…

HolyBlackCat 05.01.2020 22:40

Это не дает такого же вывода для MSVC. GCC и Clang выдают просто «Foo», если я вызываю type_name<Foo>(), тогда как MSVC выдает «class Foo».

Joakim Thorén 08.07.2020 15:33

Префикс / суффикс строки и магические числа могут изменяться. Функция stackoverflow.com/a/58331141/7163942 вычисляет их автоматически.

Val 03.09.2020 14:36

@Val: опубликуйте свое решение как отдельный ответ здесь.

einpoklum 22.10.2020 10:36

Говард Хиннант использовал магические числа для извлечения имени типа. 康 桓 瑋 предлагает префикс и суффикс строки. Но префикс / суффикс продолжают меняться. С помощью «probe_type» type_name автоматически вычисляет размеры префикса и суффикса для «probe_type» для извлечения имени типа:

#include <string_view>
using namespace std;

namespace typeName {
 template <typename T>
  constexpr string_view wrapped_type_name () {
#ifdef __clang__
    return __PRETTY_FUNCTION__;
#elif defined(__GNUC__)
    return  __PRETTY_FUNCTION__;
#elif defined(_MSC_VER)
    return  __FUNCSIG__;
#endif
  }

  class probe_type;
  constexpr string_view probe_type_name ("typeName::probe_type");
  constexpr string_view probe_type_name_elaborated ("class typeName::probe_type");
  constexpr string_view probe_type_name_used (wrapped_type_name<probe_type> ().find (probe_type_name_elaborated) != -1 ? probe_type_name_elaborated : probe_type_name);

  constexpr size_t prefix_size () {
    return wrapped_type_name<probe_type> ().find (probe_type_name_used);
  }

  constexpr size_t suffix_size () {
    return wrapped_type_name<probe_type> ().length () - prefix_size () - probe_type_name_used.length ();
  }

  template <typename T>
  string_view type_name () {
    constexpr auto type_name = wrapped_type_name<T> ();

    return type_name.substr (prefix_size (), type_name.length () - prefix_size () - suffix_size ());
  }
}

#include <iostream>

using typeName::type_name;
using typeName::probe_type;

class test;

int main () {
  cout << type_name<class test> () << endl;

  cout << type_name<const int*&> () << endl;
  cout << type_name<unsigned int> () << endl;

  const int ic = 42;
  const int* pic = &ic;
  const int*& rpic = pic;
  cout << type_name<decltype(ic)> () << endl;
  cout << type_name<decltype(pic)> () << endl;
  cout << type_name<decltype(rpic)> () << endl;

  cout << type_name<probe_type> () << endl;
}

Выход

gcc 10.2:

test
const int *&
unsigned int
const int
const int *
const int *&
typeName::probe_type

лязг 11.0.0:

test
const int *&
unsigned int
const int
const int *
const int *&
typeName::probe_type

VS 2019 версии 16.7.6:

class test
const int*&
unsigned int
const int
const int*
const int*&
class typeName::probe_type

Отличное решение! Однако class_specifier не имеет одного конечного пробела, поэтому размер префикса неверно вычисляется, поскольку он на один символ меньше, чем должен быть. Сам не могу отредактировать сообщение, так как это слишком незначительное изменение. Затем следует соответствующим образом изменить выходные данные компилятора.

Fabio A. 02.09.2020 12:46

Фактически, вы делаете предположение и качественно не отличается от @ 康 桓 瑋: предположение, что для msvc есть дополнительный префикс «class», а для GCC и clang его не существует. Если префиксы и суффиксы могут изменяться, который тоже может измениться. Так что неясно, что это лучше, чем просто делать предположения обо всем префиксе.

einpoklum 22.10.2020 23:36

... в любом случае, не могли бы вы взглянуть на предложенное мной решение ниже? И скажите, нужна ли мне дополнительная настройка MSVC?

einpoklum 23.10.2020 00:37

Почему бы просто не использовать void вместо probe_type?

康桓瑋 23.10.2020 12:55

@einpoklum: переход от магических чисел к магическим строкам к вычисляемым строкам с использованием предопределенного типа (с предположениями компилятора) делает код менее хрупким. Чтобы двигаться по этому маршруту, обратите внимание, что компиляторы используют имя типа (<probe_type_name>) или разработанный спецификатор типа (class <probe_type_name>) и вместо того, чтобы предполагать, какой компилятор делает то, что позволяет вычислять обернутый текст и определять конкретное использование (измененный код выше) и идти с ним. На второй вопрос: код хорошо работает с MSVC Visual Studio 2019 версии 16.7.6. (Не работает на направленных веб-сайтах для MSVC).

Val 26.10.2020 19:10

@ 康 桓 瑋: Использование void (или int) для probe_type работает хорошо, но предполагает использование префикса, не содержащего этих имен. Это будет казаться менее хрупким с большим количеством контекста, настроенного именами классов / пространств имен.

Val 26.10.2020 19:24

Для тех, кто все еще посещает, у меня недавно была такая же проблема, и я решил написать небольшую библиотеку на основе ответов из этого сообщения. Он предоставляет имена типов constexpr и индексы типов и протестирован на Mac, Windows и Ubuntu.

Код библиотеки здесь: https://github.com/TheLartians/StaticTypeInfo

Another take on @康桓瑋's answer (originally ), making less assumptions about the prefix and suffix specifics, and inspired by @Val's answer - but without polluting the global namespace; without any conditions; and hopefully easier to read.

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

К сожалению, имя типа заключено в текст, описывающий функцию, который в разных компиляторах различается. Например, для GCC подпись template <typename T> int foo() с типом double будет: int foo() [T = double].

Итак, как избавиться от текста-оболочки? Решение @ HowardHinnant является самым коротким и наиболее "прямым": просто используйте магические числа для каждого компилятора, чтобы удалить префикс и суффикс. Но очевидно, что это очень хрупко; и никому не нравятся магические числа в их коде. Вместо этого вы получаете значение макроса для типа с известным именем, вы можете определить, какой префикс и суффикс составляют упаковку.

#include <string_view>

template <typename T> constexpr std::string_view type_name();

template <>
constexpr std::string_view type_name<void>()
{ return "void"; }

namespace detail {

using type_name_prober = void;

template <typename T>
constexpr std::string_view wrapped_type_name() 
{
#ifdef __clang__
    return __PRETTY_FUNCTION__;
#elif defined(__GNUC__)
    return __PRETTY_FUNCTION__;
#elif defined(_MSC_VER)
    return __FUNCSIG__;
#else
#error "Unsupported compiler"
#endif
}

constexpr std::size_t wrapped_type_name_prefix_length() { 
    return wrapped_type_name<type_name_prober>().find(type_name<type_name_prober>()); 
}

constexpr std::size_t wrapped_type_name_suffix_length() { 
    return wrapped_type_name<type_name_prober>().length() 
        - wrapped_type_name_prefix_length() 
        - type_name<type_name_prober>().length();
}

} // namespace detail

template <typename T>
constexpr std::string_view type_name() {
    constexpr auto wrapped_name = detail::wrapped_type_name<T>();
    constexpr auto prefix_length = detail::wrapped_type_name_prefix_length();
    constexpr auto suffix_length = detail::wrapped_type_name_suffix_length();
    constexpr auto type_name_length = wrapped_name.length() - prefix_length - suffix_length;
    return wrapped_name.substr(prefix_length, type_name_length);
}

Смотрите на GodBolt. Это также должно работать с MSVC.

Отличное решение для изоляции кода с помощью макросов и операций с префиксом / суффиксом.

Val 26.10.2020 19:31

@Val: По сути, я просто пытался следовать «принципу наименьшего удивления».

einpoklum 26.10.2020 22:44

Копирование из этого ответа: https://stackoverflow.com/a/56766138/11502722

Мне удалось заставить этот в некотором роде работать для C++ static_assert(). Проблема здесь в том, что static_assert() принимает только строковые литералы; constexpr string_view работать не будет. Вам нужно будет принять дополнительный текст вокруг имени типа, но это работает:

template<typename T>
constexpr void assertIfTestFailed()
{
#ifdef __clang__
    static_assert(testFn<T>(), "Test failed on this used type: " __PRETTY_FUNCTION__);
#elif defined(__GNUC__)
    static_assert(testFn<T>(), "Test failed on this used type: " __PRETTY_FUNCTION__);
#elif defined(_MSC_VER)
    static_assert(testFn<T>(), "Test failed on this used type: " __FUNCSIG__);
#else
    static_assert(testFn<T>(), "Test failed on this used type (see surrounding logged error for details).");
#endif
    }
}

Выход MSVC:

error C2338: Test failed on this used type: void __cdecl assertIfTestFailed<class BadType>(void)
... continued trace of where the erroring code came from ...

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