Có thể in một loại biến trong C ++ tiêu chuẩn không?


393

Ví dụ:

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

Sản lượng dự kiến:

int

2
Dưới đây là tóm tắt về giải pháp dạng dài của Howard nhưng được triển khai với macro một dòng dị giáo : #define DEMANGLE_TYPEID_NAME(x) abi::__cxa_demangle(typeid((x)).name(), NULL, NULL, NULL). Nếu bạn cần hỗ trợ đa nền tảng: Sử dụng #ifdef, #else, #endifđể cung cấp một macro cho các nền tảng khác như MSVC.
Trevor Boyd Smith

Với yêu cầu dễ đọc hơn của con người: stackoverflow.com/questions/12877521/iêng
Ciro Santilli 冠状 病 六四 法轮功

3
Nếu bạn chỉ sử dụng điều này để gỡ lỗi, bạn có thể muốn xem xét template<typename T> void print_T() { std::cout << __PRETTY_FUNCTION__ << '\n'; }. Sau đó, sử dụng eg print_T<const int * const **>();sẽ in void print_T() [T = const int *const **]trong thời gian chạy và bảo toàn tất cả các vòng loại (hoạt động trong GCC và Clang).
Henri Menke

@Henri, __PRETTY_FUNCTION__không phải là C ++ tiêu chuẩn (yêu cầu nằm trong tiêu đề câu hỏi).
Toby Speight

Câu trả lời:


505

C ++ 11 cập nhật một câu hỏi rất cũ: In loại biến trong C ++.

Câu trả lời được chấp nhận (và tốt) là sử dụng typeid(a).name(), đâu alà tên biến.

Bây giờ trong C ++ 11 chúng ta có decltype(x), có thể biến một biểu thức thành một loại. Và decltype()đi kèm với bộ quy tắc rất thú vị của riêng mình. Ví dụ decltype(a)decltype((a))thường sẽ là các loại khác nhau (và vì những lý do chính đáng và dễ hiểu một khi những lý do đó được phơi bày).

Liệu sự tin cậy của chúng ta có typeid(a).name()giúp chúng ta khám phá thế giới mới dũng cảm này không?

Không.

Nhưng công cụ đó sẽ không phức tạp. Và đó là công cụ mà tôi đang sử dụng như một câu trả lời cho câu hỏi này. Tôi sẽ so sánh và đối chiếu công cụ mới này typeid(a).name(). Và công cụ mới này thực sự được xây dựng trên đầu trang typeid(a).name().

Vấn đề cơ bản:

typeid(a).name()

vứt bỏ vòng loại cv, tài liệu tham khảo và lvalue / rvalue-ness. Ví dụ:

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

Đối với tôi đầu ra:

i

và tôi đoán về đầu ra MSVC:

int

Tức constlà đã biến mất. Đây không phải là vấn đề QOI (Chất lượng thực hiện). Các tiêu chuẩn bắt buộc hành vi này.

Những gì tôi khuyên bạn dưới đây là:

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

sẽ được sử dụng như thế này:

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

và cho tôi kết quả đầu ra:

int const

<disclaimer>Tôi chưa thử nghiệm điều này trên MSVC. </disclaimer> Nhưng tôi hoan nghênh phản hồi từ những người làm.

Giải pháp C ++ 11

Tôi đang sử dụng __cxa_demanglecho các nền tảng không phải là MSVC theo khuyến nghị của ipapadop trong câu trả lời của mình cho các loại demangle. Nhưng trên MSVC tôi tin tưởng typeidvào việc xáo trộn tên (chưa được kiểm tra). Và lõi này được bao bọc xung quanh một số thử nghiệm đơn giản giúp phát hiện, khôi phục và báo cáo các vòng loại cv và tham chiếu đến loại đầu vào.

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

Kết quả

Với giải pháp này tôi có thể làm điều này:

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

và đầu ra là:

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

Lưu ý (ví dụ) sự khác biệt giữa decltype(i)decltype((i)). Điều thứ nhất là kiểu của lời tuyên bố của i. Cái sau là "loại" của biểu thức i . (các biểu thức không bao giờ có kiểu tham chiếu, nhưng như một quy ước decltypebiểu thị các biểu thức lvalue với các tham chiếu lvalue).

Do đó, công cụ này là một phương tiện tuyệt vời chỉ để tìm hiểu decltype, ngoài việc khám phá và gỡ lỗi mã của riêng bạn.

Ngược lại, nếu tôi chỉ xây dựng cái này typeid(a).name(), mà không thêm lại các vòng loại hoặc tham chiếu cv bị mất, đầu ra sẽ là:

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

Tức là mọi tham chiếu và vòng loại cv đều bị loại bỏ.

Cập nhật C ++ 14

Ngay khi bạn nghĩ rằng bạn đã có một giải pháp cho vấn đề đóng đinh, một người nào đó luôn thoát ra khỏi hư không và chỉ cho bạn một cách tốt hơn nhiều. :-)

Câu trả lời này của Jamboree cho thấy cách lấy tên loại trong C ++ 14 tại thời điểm biên dịch. Đó là một giải pháp tuyệt vời cho một vài lý do:

  1. Đó là thời gian biên dịch!
  2. Bạn nhận được trình biên dịch để thực hiện công việc thay vì thư viện (thậm chí là std :: lib). Điều này có nghĩa là kết quả chính xác hơn cho các tính năng ngôn ngữ mới nhất (như lambdas).

Câu trả lời của Jamboree không hoàn toàn phù hợp với VS, và tôi điều chỉnh mã của mình một chút. Nhưng vì câu trả lời này nhận được rất nhiều lượt xem, hãy dành chút thời gian để đi đến đó và nêu lên câu trả lời của anh ấy, nếu không có nó, bản cập nhật này sẽ không bao giờ xảy ra.

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

Mã này sẽ tự động sao lưu constexprnếu bạn vẫn bị mắc kẹt trong C ++ 11 cổ đại. Và nếu bạn đang vẽ trên tường hang bằng C ++ 98/03, thìnoexcept cũng bị hy sinh.

Cập nhật C ++ 17

Trong các ý kiến ​​dưới đây Lyberta chỉ ra rằng cái mới std::string_viewcó thể thay thế 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
}

Tôi đã cập nhật các hằng số cho VS nhờ vào công việc thám tử rất hay của Jive Dadson trong các bình luận bên dưới.

Cập nhật:

Hãy chắc chắn kiểm tra viết lại dưới đây để loại bỏ các số ma thuật không thể đọc được trong công thức mới nhất của tôi.


4
VS 14 CTP được in ra các loại chính xác, tôi chỉ phải thêm một #include <iostream>dòng.
Max Galkin

3
Tại sao mẫu <typename T> std :: string type_name ()? Tại sao bạn không vượt qua một loại như là một đối số?
moonman239

2
Tôi tin rằng lý do của tôi là đôi khi tôi chỉ có một loại (chẳng hạn như tham số mẫu được suy luận) và tôi không muốn phải xây dựng một trong những thứ đó một cách giả tạo (mặc dù những ngày declvalnày sẽ thực hiện công việc).
Howard Hinnant

5
@AngelusMortis: Vì tiếng Anh mơ hồ / mơ hồ so với mã C ++, tôi khuyến khích bạn sao chép / dán mã này vào trường hợp thử nghiệm của bạn với loại cụ thể mà bạn quan tâm và với trình biên dịch cụ thể mà bạn quan tâm và viết lại bằng nhiều hơn chi tiết nếu kết quả là đáng ngạc nhiên và / hoặc không đạt yêu cầu.
Howard Hinnant

3
@HowardHinnant bạn có thể sử dụng std::string_viewthay thế static_string?
Lyberta

231

Thử:

#include <typeinfo>

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

Bạn có thể phải kích hoạt RTTI trong các tùy chọn trình biên dịch để làm việc này. Ngoài ra, đầu ra của điều này phụ thuộc vào trình biên dịch. Nó có thể là một tên loại thô hoặc một biểu tượng xáo trộn tên hoặc bất cứ điều gì ở giữa.


4
Tại sao chuỗi trả về bởi hàm name () được xác định?
Kẻ hủy diệt

4
@PravasiMeet Không có lý do chính đáng, theo như tôi biết. Ủy ban chỉ đơn giản là không muốn buộc những người triển khai trình biên dịch theo các hướng kỹ thuật cụ thể - có thể là một sai lầm, trong nhận thức muộn.
Konrad Rudolph

2
Có cờ nào tôi có thể sử dụng để kích hoạt RTTI không? Có lẽ bạn có thể làm cho câu trả lời của bạn bao gồm.
Jim

4
@Destructor Cung cấp một định dạng xáo trộn tên được tiêu chuẩn hóa có thể mang lại cảm giác rằng khả năng tương tác giữa các nhị phân được xây dựng bởi hai trình biên dịch khác nhau là có thể và / hoặc an toàn, khi không. Bởi vì C ++ không có ABI tiêu chuẩn, một sơ đồ xáo trộn tên tiêu chuẩn sẽ là vô nghĩa, và có khả năng gây hiểu lầm và nguy hiểm.
Elkvis

1
@Jim Phần trên cờ trình biên dịch sẽ là một thứ tự có độ lớn dài hơn chính câu trả lời. Theo mặc định, GCC biên dịch với nó, do đó "-fno-rtti", các trình biên dịch khác có thể chọn không, nhưng không có tiêu chuẩn nào cho các cờ biên dịch.
kfsone

82

Rất xấu nhưng thực hiện thủ thuật nếu bạn chỉ muốn biên dịch thông tin thời gian (ví dụ để gỡ lỗi):

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

Trả về:

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
chỉ c ++ mới có thể làm điều này trở nên khó khăn (in một loại biến tự động tại thời gian biên dịch). CHỈ C ++.
Karl Pickett

3
@KarlP thật công bằng, nó hơi phức tạp, điều này cũng hoạt động :) auto testVar = std::make_tuple(1, 1.0, "abc"); decltype(testVar)::foo = 1;
NickV

Trên VC ++ 17, điều này làm giảm tham chiếu giá trị thành tham chiếu đơn giản, ngay cả trong hàm mẫu có tham số chuyển tiếp tham chiếu và tên đối tượng được bọc trong std :: Forward.
Jive Dadson

Bạn đã có thể đến loại mà không cần tạo bất kỳ bánh xe mới!
Steven Eckhoff

1
Kỹ thuật này cũng được mô tả trong "Mục 4: Biết cách xem các loại suy diễn" trong C ++ hiện đại hiệu quả
lenkite

54

Đừng quên bao gồm <typeinfo>

Tôi tin rằng những gì bạn đang đề cập đến là nhận dạng loại thời gian chạy. Bạn có thể đạt được những điều trên bằng cách làm.

#include <iostream>
#include <typeinfo>

using namespace std;

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

36

Theo giải pháp của Howard , nếu bạn không muốn số ma thuật, tôi nghĩ đây là một cách hay để thể hiện và trông trực quan:

#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
Đây là một sự chắt lọc rất nhiều nỗ lực trong một số phiên bản C ++ vừa qua thành một thứ gì đó ngắn và ngọt ngào. +1.
einpoklum

1
Đây là yêu thích của tôi quá!
Howard Hinnant

1
Đây là một chức năng tương tự mà tôi sử dụng, tự động phát hiện hậu tố / tiền tố: stackoverflow.com/questions/1055452/
Kẻ

22

Lưu ý rằng các tên được tạo bởi tính năng RTTI của C ++ không khả dụng. Ví dụ: lớp học

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

sẽ có các tên sau:

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

Vì vậy, bạn không thể sử dụng thông tin này để tuần tự hóa. Tuy nhiên, thuộc tính typeid (a) .name () vẫn có thể được sử dụng cho mục đích ghi nhật ký / gỡ lỗi


19

Bạn có thể sử dụng các mẫu.

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

Trong ví dụ trên, khi loại không khớp, nó sẽ in "không xác định".


3
Nó không in "int" cho quần short và chars? Và "nổi" cho đôi?
gartriese

1
@gartenriese Chuyên ngành không có nhược điểm đó. Vì doublenó sẽ biên dịch phiên bản không chuyên biệt của hàm mẫu thay vì thực hiện chuyển đổi kiểu ngầm để sử dụng chuyên môn: cpp.sh/2wzc
chappjc

1
@chappjc: Thật lòng tôi không biết tại sao tôi lại hỏi vậy, bây giờ nó khá rõ ràng với tôi. Nhưng cảm ơn vì đã trả lời một câu hỏi cũ dù sao đi nữa!
gartriese

2
@gartenriese Tôi đã tìm ra rất nhiều, nhưng "internet" có thể có cùng một câu hỏi tại một số điểm.
chappjc

18

Như đã đề cập, typeid().name()có thể trả lại một tên xéo. Trong GCC (và một số trình biên dịch khác), bạn có thể làm việc xung quanh nó với mã sau:

#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

Bạn có thể sử dụng một lớp đặc điểm cho việc này. Cái gì đó như:

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

Các DECLARE_TYPE_NAME nghĩa tồn tại để làm cho cuộc sống của bạn dễ dàng hơn trong việc khai báo lớp đặc điểm này cho tất cả các loại bạn cần.

Điều này có thể hữu ích hơn các giải pháp liên quan typeidvì bạn có thể kiểm soát đầu ra. Ví dụ: sử dụng typeidcho long longtrình biên dịch của tôi cho "x".


10

Trong C ++ 11, chúng ta có dectype. Không có cách nào trong c ++ tiêu chuẩn để hiển thị loại biến chính xác được khai báo bằng cách sử dụng dectype. Chúng ta có thể sử dụng boost typeindex tức là type_id_with_cvr(cvr là viết tắt của const, volility, Reference) để in kiểu như dưới đây.

#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
Sẽ đơn giản hơn nếu sử dụng chức năng trợ giúp:template<typename T> void print_type(T){cout << "type T is: "<< type_id_with_cvr<T>().pretty_name()<< '\n';}
r0ng

6

Bạn cũng có thể sử dụng bộ lọc c ++ với tùy chọn -t (loại) để sắp xếp tên loại:

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

Chỉ thử nghiệm trên linux.


1
Địa ngục xấu xí nhưng sẽ làm cho những gì tôi cần. Và nhỏ hơn nhiều so với các giải pháp khác. Hoạt động trên Mac btw.
Marco Luglio

6

Howard Hinnant đã sử dụng các số ma thuật để trích xuất tên loại. 康 桓 tiền tố chuỗi và hậu tố được đề xuất. Nhưng tiền tố / hậu tố cứ thay đổi. Với kiểu đầu dò của kiểu thăm dò_type, kiểu type_name sẽ tự động tính toán các kích thước tiền tố và hậu tố cho kiểu đầu dò hình chữ nhật_xe để trích xuất tên loại:

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

Đầu ra

gcc 10.0.0 20190919 Cây đũa phép:

 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]

Phiên bản 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

Các câu trả lời khác liên quan đến RTTI (typeid) có thể là những gì bạn muốn, miễn là:

  • bạn có thể đủ khả năng chi phí bộ nhớ (có thể đáng kể với một số trình biên dịch)
  • tên lớp mà trình biên dịch của bạn trả về là hữu ích

Cách khác, (tương tự như câu trả lời của Greg Hewgill), là xây dựng bảng tính trạng thời gian biên dịch.

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

Xin lưu ý rằng nếu bạn bao bọc các khai báo trong một macro, bạn sẽ gặp khó khăn khi khai báo tên cho các loại mẫu lấy nhiều hơn một tham số (ví dụ: std :: map), do dấu phẩy.

Để truy cập tên của loại biến, tất cả những gì bạn cần là

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

1
Điểm hay về dấu phẩy, tôi biết có một lý do macro là một ý tưởng tồi nhưng không nghĩ đến nó vào thời điểm đó!
Greg Hewgill

2
const const char * value = "Wibble"; bạn không thể làm người bạn đời đó :)
Johannes Schaub - litb

5

Một giải pháp chung chung hơn mà không quá tải chức năng so với giải pháp trước đây của tôi:

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

Ở đây MyClass là lớp do người dùng định nghĩa. Nhiều điều kiện có thể được thêm vào đây là tốt.

Thí dụ:

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

Đầu ra:

int
String
MyClass

5

Tôi thích phương pháp của Nick, Một hình thức hoàn chỉnh có thể là thế này (cho tất cả các loại dữ liệu cơ bản):

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) nó sẽ không hoạt động đối với các loại khác (nghĩa là không chung chung); (ii) phình mã vô dụng; (iii) tương tự có thể (chính xác) được thực hiện với typeidhoặc decltype.
edmz

2
Bạn đúng, nhưng nó bao gồm tất cả các loại cơ bản ... và đó là những gì tôi cần ngay bây giờ ..
Jahid 13/03/2015

2
Bạn có thể cho tôi biết, bạn sẽ làm thế nào với dectype không,
Jahid 13/03/2015

1
Nếu đó là một bài kiểm tra thời gian biên dịch, bạn có thể sử dụng std :: is_same <T, S> và dectype để lấy T và S.
edmz 14/03/2015

4

Khi tôi thách thức, tôi quyết định thử nghiệm xem người ta có thể đi xa đến đâu với thủ thuật mẫu độc lập với nền tảng (hy vọng).

Các tên được lắp ráp hoàn chỉnh tại thời gian biên dịch. (Điều đó có nghĩa là typeid(T).name()không thể được sử dụng, do đó bạn phải cung cấp tên rõ ràng cho các loại không kết hợp. Nếu không, các trình giữ chỗ sẽ được hiển thị thay thế.)

Ví dụ sử dụng:

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)`.
}

Mã số:

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

Đầu ra:

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

Theo giải thích của Scott Meyers trong C ++ hiện đại hiệu quả,

Các cuộc gọi đến std::type_info::namekhông được đảm bảo để trả lại bất kỳ điều gì hợp lý.

Giải pháp tốt nhất là để cho trình biên dịch tạo ra một thông báo lỗi trong quá trình khấu trừ kiểu, ví dụ,

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

Kết quả sẽ giống như thế này, tùy thuộc vào trình biên dịch,

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;

Do đó, chúng ta biết rằng xloại là int, yloại làconst int*


0

Đối với bất kỳ ai vẫn truy cập, gần đây tôi đã có cùng một vấn đề và quyết định viết một thư viện nhỏ dựa trên câu trả lời từ bài đăng này. Nó cung cấp tên loại constexpr và chỉ mục loại und được thử nghiệm trên Mac, Windows và Ubuntu.

Mã thư viện ở đây: https://github.com/TheLartians/StaticTypeInfo

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.