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

Например:

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

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

 int 

Обновление 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() .

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

 typeid(a).name() 

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

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

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

 i 

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

 int 

Т.е. const ушел. Это не вопрос QOI (Quality of Implementation). Стандарт предусматривает такое поведение.

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

 template  std::string type_name(); 

который будет использоваться следующим образом:

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

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

 int const 

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

Решение C ++ 11

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

 #include  #include  #ifndef _MSC_VER # include  #endif #include  #include  #include  template  std::string type_name() { typedef typename std::remove_reference::type TR; std::unique_ptr 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::value) r += " const"; if (std::is_volatile::value) r += " volatile"; if (std::is_lvalue_reference::value) r += "&"; else if (std::is_rvalue_reference::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() << '\n'; std::cout << "decltype((i)) is " << type_name() << '\n'; std::cout << "decltype(ci) is " << type_name() << '\n'; std::cout << "decltype((ci)) is " << type_name() << '\n'; std::cout << "decltype(static_cast(i)) is " << type_name(i))>() << '\n'; std::cout << "decltype(static_cast(i)) is " << type_name(i))>() << '\n'; std::cout << "decltype(static_cast(i)) is " << type_name(i))>() << '\n'; std::cout << "decltype(foo_lref()) is " << type_name() << '\n'; std::cout << "decltype(foo_rref()) is " << type_name() << '\n'; std::cout << "decltype(foo_value()) is " << type_name() << '\n'; } 

и выход:

 decltype(i) is int decltype((i)) is int& decltype(ci) is int const decltype((ci)) is int const& decltype(static_cast(i)) is int& decltype(static_cast(i)) is int&& decltype(static_cast(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(i)) is int decltype(static_cast(i)) is int decltype(static_cast(i)) is int decltype(foo_lref()) is int decltype(foo_rref()) is int decltype(foo_value()) is int 

Т.е. каждый ссылочный и cv-определитель удаляется.

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

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

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

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

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

 #include  #include  #include  #include  #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  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  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 на constexpr если вы все еще застряли в древнем C ++ 11. И если вы рисуете на стене пещеры с C ++ 98/03, то noexcept жертвует.

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

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

 template  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 благодаря очень приятной детективной работе Jive Dadson в комментариях ниже.

Пытаться:

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

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

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

 auto testVar = std::make_tuple(1, 1.0, "abc"); static_assert(decltype(testVar)::dummy_error, "DUMP MY TYPE" ); 

Возвращает:

 Compilation finished with errors: source.cpp: In function 'int main()': source.cpp:5:19: error: 'dummy_error' is not a member of 'std::tuple' 

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

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

 #include  #include  using namespace std; int main() { int i; cout << typeid(i).name(); return 0; } 

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

 MyNamespace::CMyContainer 

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

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

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

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

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

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

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

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

}

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

 #include  using namespace std; template  class type_name { public: static const char *name; }; #define DECLARE_TYPE_NAME(x) template<> const char *type_name::name = #x; #define GET_TYPE_NAME(x) (type_name::name) DECLARE_TYPE_NAME(int); int main() { int a = 12; cout << GET_TYPE_NAME(a) << endl; } 

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

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

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

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

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

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

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

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

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

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

 #include  #include  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().pretty_name() << '\n'; cout << "decltype((i)) is " << type_id_with_cvr().pretty_name() << '\n'; cout << "decltype(ci) is " << type_id_with_cvr().pretty_name() << '\n'; cout << "decltype((ci)) is " << type_id_with_cvr().pretty_name() << '\n'; cout << "decltype(std::move(i)) is " << type_id_with_cvr().pretty_name() << '\n'; cout << "decltype(std::static_cast(i)) is " << type_id_with_cvr(i))>().pretty_name() << '\n'; return 0; } 

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

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

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

Пример:

 #include  class MyClass{}; template std::string TypeOf(T){ std::string Type="unknown"; if(std::is_same::value) Type="int"; if(std::is_same::value) Type="String"; if(std::is_same::value) Type="MyClass"; return Type;} int main(){; int a=0; std::string s=""; MyClass my; std::cout< 

Вывод:

 int String MyClass 

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

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

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

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

Поскольку я бросаю вызов, я решил проверить, как далеко можно идти с независимым от платформы (надеюсь) шаблоном.

Имена полностью собраны во время компиляции. (Это означает, что 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 << '\n'; // -> `void (*)(int)` // Ugly mess case // Note that compiler removes cv-qualifiers from parameters and replaces arrays with pointers. std::cout << type_name << '\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 << '\n'; // -> `class? (*)(int,??)` // With appropriate TYPE_NAME's, the output would be `std::string (*)(int,short)`. } 

Код:

 #include  #include  static constexpr std::size_t max_str_lit_len = 256; template  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  struct str_lit { static constexpr char value[] {C..., '\0'}; static constexpr int size = sl_len(value); template  struct concat_impl {using type = typename concat_impl::type::template concat_impl::type;}; template  struct concat_impl> {using type = str_lit;}; template  using concat = typename concat_impl::type; }; template  struct trim_str_lit_impl; template  struct trim_str_lit_impl, S> { using type = str_lit; }; template  using trim_str_lit = typename trim_str_lit_impl, S>::type; #define STR_LIT(str) ::trim_str_lit<::sl_len(str), ::str_lit::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(str),::sl_at(str),::sl_at(str),::sl_at(str) template  constexpr str_lit make_str_lit(str_lit) {return {};} template  constexpr auto make_str_lit(const char (&str)[N]) { return trim_str_lit{}; } template  struct cexpr_pow {static constexpr std::size_t value = A * cexpr_pow::value;}; template  struct cexpr_pow {static constexpr std::size_t value = 1;}; template > struct num_to_str_lit_impl; template  struct num_to_str_lit_impl> { static constexpr auto func() { if constexpr (N >= cexpr_pow<10,X>::value) return num_to_str_lit_impl::func(); else return str_lit<(N / cexpr_pow<10,X-1-Seq>::value % 10 + '0')...>{}; } }; template  using num_to_str_lit = decltype(num_to_str_lit_impl::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; 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  inline constexpr bool ptr_or_ref = std::is_pointer_v || std::is_reference_v || std::is_member_pointer_v; template  inline constexpr bool func_or_arr = std::is_function_v || std::is_array_v; template  struct primitive_type_name {using value = unk;}; template >> using enable_if_class = T; template >> using enable_if_union = T; template >> using enable_if_enum = T; template  struct primitive_type_name> {using value = c_cla;}; template  struct primitive_type_name> {using value = c_uni;}; template  struct primitive_type_name> {using value = c_enu;}; template  struct type_name_impl; template  using type_name_lit = std::conditional_t::value::template concat, typename type_name_impl::l::template concat::r>>, typename primitive_type_name::value, typename type_name_impl::l::template concat::r>>; template  inline constexpr const char *type_name = type_name_lit::value; template  && !std::is_volatile_v>> using enable_if_no_cv = T; template  struct type_name_impl { using l = typename primitive_type_name::value::template concat; using r = str_lit<>; }; template  struct type_name_impl { using new_T_l = std::conditional_t::l::size && !ptr_or_ref, spa::concat::l>, typename type_name_impl::l>; using l = std::conditional_t, typename new_T_l::template concat, con::concat>; using r = typename type_name_impl::r; }; template  struct type_name_impl { using new_T_l = std::conditional_t::l::size && !ptr_or_ref, spa::concat::l>, typename type_name_impl::l>; using l = std::conditional_t, typename new_T_l::template concat, vol::concat>; using r = typename type_name_impl::r; }; template  struct type_name_impl { using new_T_l = std::conditional_t::l::size && !ptr_or_ref, spa::concat::l>, typename type_name_impl::l>; using l = std::conditional_t, typename new_T_l::template concat, con_vol::concat>; using r = typename type_name_impl::r; }; template  struct type_name_impl { using l = std::conditional_t, typename type_name_impl::l::template concat, typename type_name_impl::l::template concat< ast>>; using r = std::conditional_t, rpa::concat::r>, typename type_name_impl::r>; }; template  struct type_name_impl { using l = std::conditional_t, typename type_name_impl::l::template concat, typename type_name_impl::l::template concat< amp>>; using r = std::conditional_t, rpa::concat::r>, typename type_name_impl::r>; }; template  struct type_name_impl { using l = std::conditional_t, typename type_name_impl::l::template concat, typename type_name_impl::l::template concat< amp, amp>>; using r = std::conditional_t, rpa::concat::r>, typename type_name_impl::r>; }; template  struct type_name_impl { using l = std::conditional_t, typename type_name_impl::l::template concat, nsp, ast>, typename type_name_impl::l::template concat< type_name_lit, nsp, ast>>; using r = std::conditional_t, rpa::concat::r>, typename type_name_impl::r>; }; template  struct type_name_impl> { using l = typename type_name_impl::l; using r = lbr::concat::r>; }; template  struct type_name_impl> { using l = typename type_name_impl::l; using r = lbr::concat, rbr, typename type_name_impl::r>; }; template  struct type_name_impl { using l = typename type_name_impl::l; using r = lpa::concat::r>; }; template  struct type_name_impl { using l = typename type_name_impl::l; using r = lpa::concat, com::concat>..., rpa, typename type_name_impl::r>; }; #define TYPE_NAME(t) template <> struct primitive_type_name {using value = STR_LIT(#t);};

 #include  #include  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 Type of s: PKc Real type of s: char const* one two three Type of i: i Real type of i: int 
  • Как получить имя переменной в виде строки?
  • Где постоянные переменные хранятся в C?
  • Как я могу имитировать переменную массива в MySQL?
  • Как установить переменные в сценариях HIVE
  • Использование i и j в качестве переменных в Matlab
  • Создание глобальных переменных CSS: управление темами стилей
  • Как сделать точки одним цветом, когда третий столбец равен нулю, а другой цвет - в Gnuplot?
  • VBA - переменная в диапазоне, внутри формулы
  • Как вы создаете разные имена переменных в цикле?
  • Печать всех глобальных переменных / локальных переменных?
  • Значение переменной доступа с использованием строки, представляющей имя переменной в C ++
  • Давайте будем гением компьютера.