เป็นไปได้ไหมที่จะพิมพ์ตัวแปรใน 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
Trevor Boyd Smith

ด้วยความต้องการที่อ่านได้ของมนุษย์ที่ชัดเจนยิ่งขึ้น: stackoverflow.com/questions/12877521/ …
Ciro Santilli 法轮功冠状病病六四事件法轮功

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 Menke

@Henri __PRETTY_FUNCTION__ไม่ใช่ Standard 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-qualifiers, การอ้างอิงและ 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-qualifier หรือการอ้างอิงที่หายไปกลับมาผลลัพธ์จะเป็น:

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

Ie ทุกการอ้างอิงและ CV-qualifier ถูกตัดออก

การอัปเดต C ++ 14

เมื่อคุณคิดว่าคุณมีทางออกสำหรับปัญหาที่ถูกจับใครบางคนมักจะมาจากที่ไหนและแสดงให้คุณเห็นวิธีที่ดีกว่ามาก :-)

คำตอบจากJamboree นี้แสดงวิธีรับชื่อชนิดใน C ++ 14 ในเวลารวบรวม มันเป็นทางออกที่ยอดเยี่ยมด้วยเหตุผลสองประการ:

  1. มันเป็นเวลารวบรวม!
  2. คุณได้รับคอมไพเลอร์เองเพื่อทำงานแทนไลบรารี (แม้แต่ std :: lib) นี่หมายถึงผลลัพธ์ที่แม่นยำยิ่งขึ้นสำหรับฟีเจอร์ภาษาล่าสุด (เช่น lambdas)

คำตอบของ Jamboreeไม่ได้วางโครงสร้างทุกอย่างให้กับ 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/03noexceptเสียสละก็เช่นกัน

การอัปเดต 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 ด้วยการทำงานของนักสืบที่ดีมากโดย Jive Dadson ในความคิดเห็นด้านล่าง

ปรับปรุง:

ให้แน่ใจว่าได้ตรวจสอบการเขียนด้านล่างนี้ซึ่งกำจัดตัวเลขมายากลที่อ่านไม่ได้ในสูตรล่าสุดของฉัน


4
VS 14 CTP พิมพ์ประเภทที่ถูกต้องฉันต้องเพิ่มเพียงหนึ่ง #include <iostream>บรรทัด
Max Galkin

3
ทำไมเทมเพลต <typename T> std :: string type_name () ทำไมคุณไม่ผ่านการโต้แย้งประเภทหนึ่ง?
moonman239

2
ฉันเชื่อว่าเหตุผลของฉันคือบางครั้งฉันมีเพียงประเภท (เช่นพารามิเตอร์เทมเพลตที่อนุมานได้) และฉันไม่ต้องการที่จะสร้างสิ่งเหล่านี้เพื่อให้ได้มาซึ่งประเภท (แม้ว่าวันนี้declvalจะทำงาน)
Howard Hinnant

5
@AngelusMortis: เนื่องจากภาษาอังกฤษนั้นคลุมเครือ / คลุมเครือเมื่อเทียบกับรหัส C ++ ฉันขอแนะนำให้คุณคัดลอก / วางในกรณีทดสอบของคุณด้วยประเภทเฉพาะที่คุณสนใจและด้วยคอมไพเลอร์เฉพาะที่คุณสนใจและเขียนกลับมาด้วยอีก รายละเอียดหากผลลัพธ์นั้นน่าประหลาดใจและ / หรือไม่น่าพอใจ
Howard Hinnant

3
@HowardHinnant คุณสามารถใช้std::string_viewแทนได้static_stringหรือไม่
Lyberta

231

ลอง:

#include <typeinfo>

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

คุณอาจต้องเปิดใช้งาน RTTI ในตัวเลือกคอมไพเลอร์เพื่อให้ทำงานได้ นอกจากนี้ผลลัพธ์ของสิ่งนี้ขึ้นอยู่กับคอมไพเลอร์ อาจเป็นชื่อชนิด raw หรือสัญลักษณ์ mangling หรืออะไรก็ได้


4
ทำไมการส่งคืนสตริงด้วยชื่อ () ฟังก์ชั่นถูกกำหนดไว้?
Destructor

4
@PravasiMeet ไม่มีเหตุผลที่ดีเท่าที่ฉันรู้ คณะกรรมการไม่ต้องการบังคับให้ผู้รวบรวมคอมไพเลอร์ไปสู่ทิศทางเทคนิคเฉพาะ - อาจผิดพลาดในการเข้าใจปัญหา
Konrad Rudolph

2
มีการตั้งค่าสถานะที่ฉันสามารถใช้เพื่อเปิดใช้งาน RTTI หรือไม่ บางทีคุณอาจให้คำตอบของคุณรวม
Jim

4
@Destructor การจัดรูปแบบชื่อ mangling ที่เป็นมาตรฐานอาจให้ความรู้สึกว่าการทำงานร่วมกันระหว่างไบนารีที่สร้างโดยคอมไพเลอร์สองตัวที่แตกต่างกันนั้นเป็นไปได้และ / หรือปลอดภัยเมื่อไม่มี เนื่องจาก C ++ ไม่มี ABI มาตรฐานรูปแบบชื่อ mangling มาตรฐานจะไม่มีจุดหมายและอาจทำให้เข้าใจผิดและเป็นอันตราย
Elkvis

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 ++
Karl Pickett

3
@KarlP ดีที่จะยุติธรรมมันซับซ้อนเล็กน้อยก็ใช้งานได้เช่นกัน :) auto testVar = std::make_tuple(1, 1.0, "abc"); decltype(testVar)::foo = 1;
NickV

บน VC ++ 17 สิ่งนี้จะลดการอ้างอิง rvalue ไปเป็นการอ้างอิงธรรมดาแม้ในฟังก์ชั่นแม่แบบที่มีพารามิเตอร์การส่งต่อการอ้างอิงและชื่อวัตถุห่อใน std :: ไปข้างหน้า
Jive Dadson

คุณสามารถไปที่ประเภทโดยไม่ต้องสร้างล้อใหม่!
Steven Eckhoff

1
เทคนิคนี้ยังได้อธิบายไว้ใน "รายการ 4: รู้วิธีดูประเภทการอนุมาน" ใน Effective Modern C ++
lenkite

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
นี่คือสิ่งที่ฉันชอบด้วย!
Howard Hinnant

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 () ยังสามารถใช้เพื่อวัตถุประสงค์ในการเข้าสู่ระบบ / debug


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 Specialisation ไม่มีข้อเสียเปรียบนั้น เพราะ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;
}

DECLARE_TYPE_NAMEกำหนดมีอยู่เพื่อทำให้ชีวิตของคุณง่ายขึ้นในการประกาศคลาสลักษณะนี้สำหรับทุกประเภทที่คุณต้องการ

สิ่งนี้อาจมีประโยชน์มากกว่าโซลูชันที่เกี่ยวข้องtypeidเนื่องจากคุณสามารถควบคุมเอาต์พุตได้ ตัวอย่างเช่นการใช้typeidสำหรับlong longคอมไพเลอร์ของฉันให้ "x"


10

ใน C ++ 11 เรามีรูปแบบ ไม่มีวิธีใดในมาตรฐาน 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

Howard Hinnantใช้หมายเลขมายากลเพื่อแยกชื่อประเภทpre 桓瑋คำนำหน้าสตริงที่แนะนำและคำต่อท้าย แต่คำนำหน้า / คำต่อท้ายมีการเปลี่ยนแปลง ด้วย“ 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>]

เสียงดัง 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) อาจเป็นสิ่งที่คุณต้องการตราบใดที่:

  • คุณสามารถจ่ายค่าโสหุ้ยหน่วยความจำได้
  • ชั้นชื่อรวบรวมผลตอบแทนของคุณมีประโยชน์

ทางเลือก (คล้ายกับคำตอบของ Greg Hewgill) คือการสร้างตารางเวลารวบรวมคุณสมบัติ

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
จุดดีเกี่ยวกับเครื่องหมายจุลภาคฉันรู้ว่ามีเหตุผลมาโครเป็นความคิดที่ไม่ดี แต่ไม่ได้คิดในเวลานั้น!
Greg Hewgill

2
char const แบบคงที่ * value = "Wibble"; คุณไม่สามารถทำอย่างนั้นได้ :)
โยฮันเนส Schaub - litb

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

ฉันชอบวิธีของ Nick รูปแบบที่สมบูรณ์อาจเป็นแบบนี้ (สำหรับประเภทข้อมูลพื้นฐานทั้งหมด):

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
คุณพูดถูก แต่ครอบคลุมทุกประเภทพื้นฐาน ... และนั่นคือสิ่งที่ฉันต้องการในตอนนี้ ..
Jahid

2
คุณบอกฉันได้ไหมว่าคุณจะทำอย่างไรกับคนประเภทเดวิส
Jahid

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

ตามที่อธิบายโดย Scott Meyers ใน Effective Modern 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's ชนิดint, y' s ประเภทคือconst int*


0

สำหรับผู้ที่ยังคงเยี่ยมชมฉันเพิ่งมีปัญหาเดียวกันและตัดสินใจที่จะเขียนห้องสมุดขนาดเล็กตามคำตอบจากโพสต์นี้ มันมีชื่อประเภท constexpr และดัชนีชนิด und มีการทดสอบบน Mac, Windows และ Ubuntu

รหัสห้องสมุดอยู่ที่นี่: https://github.com/TheLartians/StaticTypeInfo

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.