Чи можливо надрукувати тип змінної у стандартному C ++?


393

Наприклад:

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

Очікуваний вихід:

int

2
Ось короткий виклад довгих форм вирішення Говарда , але реалізується з єретичної одного рядка макросу: #define DEMANGLE_TYPEID_NAME(x) abi::__cxa_demangle(typeid((x)).name(), NULL, NULL, NULL). Якщо вам потрібна підтримка крос-платформної: Використовуйте #ifdef, #else, #endifщоб надати один макрос для інших платформ , таких як MSVC.
Тревор Бойд Сміт

З більш чітко читаним вимогою людини: stackoverflow.com/questions/12877521 / ...
Чіро Сантіллі郝海东冠状病六四事件法轮功

3
Якщо ви використовуєте це лише для налагодження, ви можете розглянути 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, __PRETTY_FUNCTION__не є стандартом C ++ (вимога вказана в назві питання).
Toby Speight

Відповіді:


505

Оновлення 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 платформ, як рекомендує ipapadop у своїй відповіді на типи демонтажу . Але в 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

Тобто, кожне посилання та рейтинговий рейтинг вимкнено.

C ++ 14 Оновлення

Тільки коли ти думаєш, що ти вирішив проблему, яка прибита, хтось завжди з’являється з нізвідки і показує тобі набагато кращий спосіб. :-)

Ця відповідь від Jamboree показує, як отримати ім'я типу в 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 Оновлення

У коментарях нижче Ліберта вказує, що нове 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 завдяки дуже приємній детективній роботі Джіве Дадсона в коментарях нижче.

Оновлення:

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


4
VS 14 CTP надрукував правильні типи, мені довелося додати лише один #include <iostream>рядок.
Макс Галкін

3
Чому шаблон <typename T> std :: string type_name ()? Чому ти не передаєш тип як аргумент?
moonman239

2
Я вважаю , що моє пояснення в тому , що іноді я тільки мав типу (наприклад, виведений параметр шаблону), і я не хочу, щоб штучно побудувати одну з них , щоб отримати тип (хоча в ці дні declvalбудуть робити цю роботу).
Говард Хінант

5
@AngelusMortis: Оскільки англійська мова нечітка / неоднозначна порівняно з кодом C ++, я рекомендую вам скопіювати / вставити це у свій тестовий випадок із типом, який вас цікавить, та із певним компілятором, який вас цікавить, і написати ще більше деталі, якщо результат є дивовижним та / або незадовільним.
Говард Хінант

3
@HowardHinnant ви можете використовувати std::string_viewзамість static_string?
Ліберта

231

Спробуйте:

#include <typeinfo>

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

Можливо, вам доведеться активувати RTTI в параметрах компілятора, щоб це працювало. Крім того, результат цього залежить від компілятора. Це може бути вихідне ім'я типу або символ маніпулювання іменем або щось середнє.


4
Чому рядок, що повертається функцією name (), визначено реалізацією?
Деструктор

4
@PravasiMeet Немає вагомих причин, наскільки я знаю. Комітет просто не хотів змушувати виконавців-компіляторів до конкретних технічних вказівок - можливо, помилка, заднім числом.
Конрад Рудольф

2
Чи є прапор, який я міг би використовувати для включення RTTI? Можливо, ви могли б зробити свою відповідь включно.
Джим

4
@Destructor Надання стандартизованого формату керування іменами може створити враження, що сумісність між бінарними файлами, побудованими двома різними компіляторами, можлива та / або безпечна, якщо її немає. Оскільки у C ++ немає стандартного ABI, стандартна схема керування іменами була б безглуздою та потенційно оманливою та небезпечною.
Елквіс

1
@Jim Розділ про прапорці компілятора був би на порядок довший, ніж сама відповідь. GCC компілюється з ним за замовчуванням, отже, "-fno-rtti", інші компілятори можуть вирішити "не робити", але немає стандартів для прапорців компілятора.
kfsone

82

Дуже некрасиво, але це хитрість, якщо ви хочете лише збирати інформацію про час (наприклад, для налагодження):

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*>'

2
тільки c ++ може зробити це настільки складним (друк типу автоматичних змінних під час компіляції). ТІЛЬКИ C ++.
Карл Пікетт

3
@KarlP добре, щоб бути справедливим, це трохи auto testVar = std::make_tuple(1, 1.0, "abc"); decltype(testVar)::foo = 1;
перекручено

У VC ++ 17 це зменшує посилання rvalue до простої посилання, навіть у функції шаблону з переадресаційним параметром та ім'ям об'єкта, загорнутим у std :: forward.
Jive Dadson

Ви змогли дістатися до типу, не створюючи нових коліс!
Стівен Екгофф

1
Ця методика також описана в "Пункт 4: Знати, як переглянути виведені типи" в "Ефективний сучасний C ++"
ленкіт

54

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

Я вважаю, що ви маєте на увазі ідентифікацію типу виконання. Ви можете досягти сказаного, роблячи.

#include <iostream>
#include <typeinfo>

using namespace std;

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

36

Згідно з рішенням Говарда , якщо ви не хочете чарівного числа, я думаю, що це хороший спосіб представити і виглядати інтуїтивно:

#include <string_view>

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

4
Це велика дистиляція зусиль за останні кілька C ++ версій у щось коротке і миле. +1.
einpoklum

1
Це теж мій улюблений!
Говард Хіннант

1
Тут аналогічна функція , яку я використовую, що визначає суфікс / префікс автоматично: stackoverflow.com/questions/1055452 / ...
HolyBlackCat

22

Зауважте, що імена, створені функцією 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 () все ще може використовуватися для цілей журналу / налагодження


19

Ви можете використовувати шаблони.

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

У наведеному вище прикладі, коли тип не збігається, він надрукує "невідомо".


3
Чи не надрукується "int" для шортів та символів? І "пливе" для парних?
gartenriese

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

1
@chappjc: Я, чесно кажучи, не знаю, чому я тоді це запитував, зараз мені це досить зрозуміло. Але дякую за те, що відповіли на річне запитання все одно!
gartenriese

2
@gartenriese Я подумав стільки ж, але в "Інтернеті" в якийсь момент може виникнути те саме питання.
chappjc

18

Як уже згадувалося, 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;

}


10

Ви можете використовувати для цього клас ознак. Щось на зразок:

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

The DECLARE_TYPE_NAME існує, щоб полегшити ваше життя при оголошенні цього класу ознак для всіх типів, які ви очікуєте потрібні.

Це може бути корисніше, ніж рішення, пов'язані з typeidтим, що ви можете контролювати вихід. Наприклад, використання typeidдля long longмого компілятора дає "x".


10

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

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

6

Ви також можете використовувати c ++ filt з опцією -t (type) для демонтажу імені типу:

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


1
Пекло некрасиво, але зроблять те, що мені потрібно. І набагато менше, ніж інші рішення. Працює на Mac btw.
Marco Luglio

6

Говард Хінант використовував магічні числа для вилучення імені типу. 瑋 桓 瑋 запропонований рядковий префікс і суфікс. Але префікс / суфікс постійно змінюються. За допомогою "probe_type" type_name автоматично обчислює розміри префікса та суфікса для "probe_type" для вилучення імені типу:

#include <iostream>
#include <string_view>

using namespace std;

class probe_type;

template <typename T>
constexpr string_view type_name() {
  string_view probe_type_name("class probe_type");
  const string_view class_specifier("class");

  string_view name;
#ifdef __clang__
  name = __PRETTY_FUNCTION__;
  probe_type_name.remove_prefix(class_specifier.length());
#elif defined(__GNUC__)
  name = __PRETTY_FUNCTION__;
  probe_type_name.remove_prefix(class_specifier.length());
#elif defined(_MSC_VER)
  name = __FUNCSIG__;
#endif

  if (name.find(probe_type_name) != string_view::npos)
    return name;

  const string_view probe_type_raw_name = type_name<probe_type>();

  const size_t prefix_size = probe_type_raw_name.find(probe_type_name);

  name.remove_prefix(prefix_size);
  name.remove_suffix(probe_type_raw_name.length() - prefix_size - probe_type_name.length());

  return name;
}

class test;

int main() {
  cout << type_name<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.0.0 20190919 Wandbox:

 test
 const int *&
 unsigned int
 const int
 const int *
 const int *&
 constexpr std::string_view type_name() [with T = probe_type; std::string_view = std::basic_string_view<char>]

clang 10.0.0 Wandbox:

 test
 const int *&
 unsigned int
 const int
 const int *
 const int *&
 std::__1::string_view type_name() [T = probe_type]

VS 2019 версія 16.3.3:

class test
const int*&
unsigned int
const int
const int*
const int*&
class std::basic_string_view<char,struct std::char_traits<char> > __cdecl type_name<class probe_type>(void)

5

Інші відповіді, що стосуються 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;
}

1
Хороший пункт про кому, я знав, що є причина, що макроси були поганою ідеєю, але не думали про це в той час!
Грег Хьюгілл

2
статичний const char * value = "Wibble"; Ви не можете цього зробити :)
Йоханнес Шауб - ліб

5

Більш загальне рішення без перевантаження функцій, ніж моє попереднє:

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

5

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

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

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

2
Ви маєте рацію, але вона охоплює всі основні типи ... і саме це мені зараз потрібно ..
Джахід

2
Чи можете ви сказати мені, як би ви зробили це з дельтиптером,
Джахід

1
Якщо це тест часу компіляції, ви можете використовувати std :: is_same <T, S> та decltype, щоб отримати T і S.
edmz

4

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

Назви збираються повністю під час компіляції. (Що означає, що 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);};

2
#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

2

Як пояснив Скотт Майєрс в "Ефективних сучасних C ++",

Заклики std::type_info::nameне гарантують повернення будь-якого розумного.

Найкраще рішення - дозволити компілятору генерувати повідомлення про помилку під час виведення типу, наприклад,

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*


0

Для тих, хто все ще відвідує, у мене нещодавно вийшов той самий випуск і вирішив написати невелику бібліотеку на основі відповідей з цієї публікації. Він надає імена типів constexpr та індекси типів, проте тестується на Mac, Windows та Ubuntu.

Код бібліотеки тут: https://github.com/TheLartians/StaticTypeInfo

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.