2009-09-28 34 views
32

Có thể trong C + + để xâu chuỗi các đối số mẫu không? Tôi đã thử điều này:Xâu chuỗi các đối số mẫu

#define STRINGIFY(x) #x 

template <typename T> 
struct Stringify 
{ 
    Stringify() 
    { 
      cout<<STRINGIFY(T)<<endl; 
    } 
}; 

int main() 
{ 
    Stringify<int> s; 
} 

Nhưng điều tôi nhận được là 'T' chứ không phải 'int'. Dường như các bộ tiền xử lý khởi động trước độ phân giải mẫu.

Có cách nào khác để thực hiện việc này không?

Có cách nào để xử lý trước diễn ra sau khi phân giải mẫu không? (Trình biên dịch là VC++).

+3

Độ phân giải mẫu sẽ khởi động trong * dài * sau khi bộ xử lý trước thực hiện công việc của nó. Dù sao, các mẫu được nhiều hơn thay thế văn bản (tốt, nó thậm chí không thay thế văn bản), do đó, thay đổi thứ tự của các hoạt động nó sẽ không giải quyết vấn đề của bạn. –

+5

Bộ tiền xử lý khởi động trước khá nhiều * mọi thứ *. Do đó tên ** trước ** - bộ vi xử lý. –

+2

Tôi đã thấy mọi người làm 'template char const * get_type_name() {return __PRETTY_FUNCTION__; } 'và sau đó giải nén' T = ... 'ra khỏi chuỗi. –

Trả lời

30

Bạn có thể thử

typeid(T).name() 

Sửa: Cố định dựa trên ý kiến.

+6

Chỉ cần lưu ý các trình biên dịch không nhất thiết phải đưa ra một giá trị trả về có nghĩa là cho 'name()', nhưng hầu hết là làm. – GManNickG

+5

Điều này phải là 'typeid()', không phải 'typeinfo()' - sau này là tên của tiêu đề '', và cũng 'std :: type_info' là kiểu lớp của đối tượng được trả về bởi' typeid() ' . –

11

Không, bạn không thể làm việc trên các loại như thể chúng là biến. Bạn có thể viết mã trích xuất typeid() của một phần tử và in tên, nhưng giá trị kết quả có thể sẽ không phải là những gì bạn mong đợi (loại tên không được standarized).

Bạn cũng có thể làm việc với mẫu chuyên ngành (và một số ma thuật macro) để đạt được một phiên bản thú vị hơn nếu số lượng các loại bạn muốn làm việc với bị hạn chế:

template <typename T> const char* printtype(); // not implemented 

// implement specializations for given types 
#define DEFINE_PRINT_TYPE(type) \ 
template<>\ 
const char* printtype<type>() {\ 
    return #type;\ 
} 
DEFINE_PRINT_TYPE(int); 
DEFINE_PRINT_TYPE(double); 
// ... and so on 
#undef DEFINE_PRINT_TYPE 
template <typename T> void test() 
{ 
    std::cout << printtype<T>() << std::endl; 
} 
int main() { 
    test<int>(); 
    test<double>(); 
    test<float>(); // compilation error, printtype undefined for float 
} 

Hoặc thậm chí bạn có thể kết hợp cả hai phiên bản : triển khai mẫu chung printtype bằng cách sử dụng typeinfo và sau đó cung cấp các chuyên môn cho các loại bạn muốn có tên fancier.

template <typename T> 
const char* printtype() 
{ 
    return typeid(T).name(); 
} 
+0

Bạn muốn trả về "T" từ type_constructor tôi nghĩ, hoặc viết "typeid (T) .name()". Nếu nó trả về một tham chiếu, typeid sẽ đánh giá cuộc gọi hàm và nhìn vào vtable/etc của "đối tượng". –

+0

Tôi nghĩ về điều đó, nhưng nó đã làm việc với các bài kiểm tra ngây thơ tôi đã tạo ra (không ai trong số họ tham chiếu vào một cơ sở của một hệ thống phân cấp với rtti), cảm ơn. –

+0

Điểm tốt về tính đa hình. Sau khi nhìn lên, tôi thấy tiêu chuẩn nói rằng đối với giá trị với các loại không đa hình, toán hạng cũng không được đánh giá. –

23

Bạn có thể sử dụng một số mẫu ma thuật.

#include <iostream> 

template <typename T> 
struct TypeName { static const char *name; }; 

template <typename T> 
const char *TypeName<T>::name = "unknown"; 

template <> 
const char *TypeName<int>::name = "int"; 

template <typename T> 
struct Stringify 
{ 
    Stringify() 
    { 
      std::cout << TypeName<T>::name << std::endl; 
    } 
}; 

int main() 
{ 
    Stringify<int> s; 
} 

Điều này có lợi thế hơn RTTI (tức là typeinfo) - nó được giải quyết trong quá trình biên dịch; và bất lợi - bạn cần phải cung cấp thông tin loại cho mình (trừ khi có một số thư viện mà đã làm điều đó mà tôi không biết, có thể một cái gì đó trong Boost thậm chí).

Hoặc, như Matrin York gợi ý trong các ý kiến, sử dụng chức năng inline các mẫu thay vì:

template <typename T> 
inline const char* typeName(void) { return "unknown"; } 

template <> 
inline const char* typeName<int>(void) { return "int"; } 

// ... 
std::cout << typeName<T>() << std::endl; 

Nhưng, nếu bạn sẽ bao giờ cần phải lưu trữ nhiều thông tin về loại đặc biệt, sau đó lớp mẫu có thể sẽ được tốt hơn.

+2

Thay vì tạo biến. Xây dựng các hàm nội tuyến trả về chuỗi thích hợp. Sau đó, bạn sẽ không có vấn đề đa definintions có thể trồng với phương pháp này. –

+0

Ý tưởng hay, tôi đã chỉnh sửa câu trả lời của mình. –

+0

Thật thú vị, các chuyên môn mẫu cho từng loại sở thích có thể dễ dàng nhất và được thực thi bằng một macro: '#define TYPE_STRING (T) mẫu <> const char * TypeName :: name = STRINGIFY (T)' – Novelocrat

15

Mã của bạn không hoạt động vì bộ tiền xử lý, chịu trách nhiệm tìm kiếm và mở rộng các macro bạn sử dụng trong mã của mình, không biết chính ngôn ngữ đó. Nó chỉ là một trình phân tích cú pháp văn bản. Nó thấy rằng STRINGIFY (T) trong khuôn mẫu hàm rất và mở rộng nó, nhiều trước khi bạn đưa ra một kiểu cho khuôn mẫu đó. Khi nó quay ra, bạn sẽ luôn luôn nhận được "T" thay vì typename bạn mong đợi, không may.

Như litb đề nghị, tôi đã (nặng) thực hiện này `getTypeName' hàm mẫu trả về typename bạn vượt qua nó:

#include <iostream> 

template <typename _Get_TypeName> 
const std::string &getTypeName() 
{ 
    static std::string name; 

    if (name.empty()) 
    { 
     const char *beginStr = "_Get_TypeName ="; 
     const size_t beginStrLen = 15; // Yes, I know... 
             // But isn't it better than strlen()? 

     size_t begin,length; 
     name = __PRETTY_FUNCTION__; 

     begin = name.find(beginStr) + beginStrLen + 1; 
     length = name.find("]",begin) - begin; 
     name = name.substr(begin,length); 
    } 

    return name; 
} 

int main() 
{ 
    typedef void (*T)(int,int); 

    // Using getTypeName() 
    std::cout << getTypeName<float>() << '\n'; 
    std::cout << getTypeName<T>() << '\n'; // You don't actually need the 
              // typedef in this case, but 
              // for it to work with the 
              // typeid below, you'll need it 

    // Using typeid().name() 
    std::cout << typeid(float).name() << '\n'; 
    std::cout << typeid(T).name() << '\n'; 

    return 0; 
} 

Đoạn mã trên kết quả ở đầu ra sau đây với -s GCC cờ ("dải tất cả các biểu tượng từ nhị phân") được kích hoạt:

float 
void (*)(int, int) 
f 
PFviiE 

Vì vậy, bạn sẽ thấy, getTypename() làm một công việc khá tốt, với chi phí mà fugly chuỗi phân tích cú pháp Hack (tôi biết, đó là chết tiệt xấu xí).

Một vài điểm để đưa vào tài khoản:

  • Mã này chỉ là GCC. Tôi không biết làm thế nào để chuyển nó sang trình biên dịch khác. Có lẽ chỉ có một vài người khác có một cơ sở như vậy để sản xuất tên chức năng khá đẹp, và từ những gì tôi tìm kiếm, MSVC++ không có một, nếu bạn đang tự hỏi mình điều đó.
  • Nếu, trong một phiên bản mới, GCC định dạng __PRETTY_FUNCTION__ của một cách khác nhau, kết hợp chuỗi có thể phá vỡ và bạn sẽ phải sửa chữa nó. Vì lý do này tôi cũng cảnh báo rằng getTypeName() có thể là tốt để gỡ lỗi (và, vẫn có thể không tốt cho điều đó), nhưng nó là chắc chắn xấu, xấu và xấu cho các mục đích khác như so sánh hai loại trong một mẫu hoặc một cái gì đó như thế (tôi không biết, chỉ đoán những gì ai đó có thể nghĩ đến ..). Sử dụng nó chỉ để gỡ lỗi, và ưu tiên không gọi nó trong bản phát hành bản phát hành (sử dụng macro để vô hiệu hóa), do đó bạn không sử dụng __PRETTY_FUNCTION__ và do đó trình biên dịch không tạo chuỗi cho nó.
  • Tôi chắc chắn không có chuyên gia, và tôi không chắc chắn liệu một số loại lẻ có thể gây ra chuỗi phù hợp với thất bại. Tôi muốn hỏi những người đọc bài này để bình luận nếu họ biết về một trường hợp như vậy.
  • Mã sử ​​dụng chuỗi tĩnh std ::. Nó có nghĩa là, nếu một số ngoại lệ được ném từ constructor hoặc destructor của nó, không có cách nào mà nó sẽ đạt đến một khối catch và bạn sẽ nhận được một ngoại lệ unhandled. Tôi không biết liệu std :: strings có thể làm điều đó, nhưng hãy cẩn thận rằng, nếu họ làm, bạn có khả năng gặp rắc rối. Tôi sử dụng nó bởi vì nó cần một destructor để giải phóng bộ nhớ. Bạn có thể thực hiện lớp của riêng bạn cho rằng, mặc dù, đảm bảo không có ngoại lệ được ném ngoài thất bại phân bổ (đó là khá nhiều chết người, phải không? Vì vậy ...), và trả về một chuỗi C đơn giản.
  • Với typedefs bạn có thể nhận được một số kết quả kỳ lạ, như thế này (đối với một số lý do, trang web phá vỡ định dạng của đoạn này, vì vậy tôi đang sử dụng dán liên kết này): http://pastebin.com/f51b888ad

Mặc dù những nhược điểm, tôi Tôi muốn nói rằng nó chắc chắn là nhanh chóng. Lần thứ hai bạn tra cứu cho cùng một tên kiểu, nó sẽ tốn chi phí khi chọn một tham chiếu đến một chuỗi std toàn cầu :: chứa tên. Và, tương đối so với các phương pháp mẫu đặc biệt được đề xuất trước đây, không có gì khác bạn phải khai báo bên cạnh chính khuôn mẫu đó, vì vậy nó thực sự dễ sử dụng hơn nhiều.

+1

w.r.t. bình luận của bạn về 'strlen', tại sao không sử dụng' const char beginStr [] = "_Get_TypeName ="; 'mà sẽ cho phép bạn sử dụng' sizeof' trừ khi nó phân rã thành một con trỏ. –

+0

Tôi nghĩ bạn nói đúng, tôi có thể làm điều đó. –

+1

Đây là giải pháp tốt nhất cho đến nay, nhưng bạn không nhận được các vòng lặp đi lặp lại sạch của biểu tượng mã nguồn khi bạn sử dụng các lớp tiêu chuẩn templated, như chuỗi. 'getTypeName ()' in ra 'std :: basic_string , std :: allocator >'. –

4

Điều này vi phạm một trong các nguyên lý chính của tôi về viết mã C++: Tránh sử dụng các thủ thuật trong cả hai tính năng mẫu và bộ xử lý trước cùng một lúc.

Một phần lý do cho các mẫu và sự tiện lợi mà chúng đưa vào ngôn ngữ là một nỗ lực để thu hút các nhà phát triển tránh xa việc sử dụng bộ tiền xử lý. Nếu bạn sử dụng cả hai, thì bọn khủng bố sẽ thắng.

+2

Tôi không đồng ý. Macros thực sự có thể rất xấu, nhưng chúng cũng có thể rất mạnh. dribeas cho thấy điều này rất độc đáo (http://stackoverflow.com/questions/1488186/1488216#1488216), kết hợp các mẫu wit tiền xử lý trước. So sánh điều đó với ý tưởng của PiotrLegnica (http://stackoverflow.com/questions/1488186/1488250#1488250), về cơ bản, giống nhau, nhưng không có macro. Tôi sẽ dùng giải pháp macro để nhập bất kỳ ngày nào. – sbi

+0

Không cần phải sợ hãi nó trên cơ sở này một mình. Macro và mẫu có thể tạo cấu trúc mạnh mẽ. –

+0

"Nếu bạn sử dụng cả hai, thì bọn khủng bố sẽ thắng." -> Bạn đang chơi quá nhiều Counter-Strike. Các macro và các mẫu được kết hợp với nhau và được sử dụng đúng cách có thể thực sự giúp bạn khắc phục những loại nào. Đừng quên rằng các hàm giả chấp nhận các kiểu như các đối số chỉ có thể đạt được theo cách này. :) –

0

Dưới đây là những gì tôi làm: Tôi có một chức năng demangle() (thực hiện trên đầu trang của abi::__cxa_demangle() mà tôi gọi với một vài quá tải hàm mẫu thuận tiện, nameof(), với một trong hai loại tôi muốn chuyển đổi thành chuỗi hoặc một thể hiện của cùng

.

đó là khá nhỏ gọn, vì vậy tôi sẽ tái tạo nó ở đây trong tất cả vinh quang của nó trong demangle.hh ta có:.

#pragma once 
#include <typeinfo> 

namespace terminator { 

    /// actual function to demangle an allegedly mangled thing 
    char const* demangle(char const* const symbol) noexcept; 

    /// convenience function template to stringify a name of a type, 
    /// either per an explicit specialization: 
    ///  char const* mytypename = terminator::nameof<SomeType>(); 
    template <typename NameType> 
    char const* nameof() { 
     try { 
      return demangle(typeid(NameType).name()); 
     } catch (std::bad_typeid const&) { 
      return "<unknown>"; 
     } 
    } 

    /// … or as implied by an instance argument: 
    ///  char const* myinstancetypename = terminator::nameof(someinstance); 
    template <typename ArgType> 
    char const* nameof(ArgType argument) { 
     try { 
      return demangle(typeid(argument).name()); 
     } catch (std::bad_typeid const&) { 
      return "<unknown>"; 
     } 
    } 

} /* namespace terminator */ 

... Và sau đó trong demangle.cpp:

#include "demangle.hh" 

#include <cstdlib> 
#include <cxxabi.h> 
#include <mutex> 
#include <memory> 

namespace terminator { 

    namespace { 

     /// define one singular, private, static std::mutex, 
     /// to keep the demangler from reentering itself 
     static std::mutex mangle_barrier; 

     /// define a corresponding private and static std::unique_ptr, 
     /// using a delete-expression to reclaim the memory malloc()'ed by 
     /// abi::__cxa_demangle() upon its return. 
     /// … we use clang pragmas to add flags locally for this to work: 
     #pragma clang diagnostic push 
     #pragma clang diagnostic ignored "-Wglobal-constructors" 
     #pragma clang diagnostic ignored "-Wexit-time-destructors" 
     std::unique_ptr<char, decltype(std::free)&> demangled_name{ nullptr, std::free }; 
     #pragma clang diagnostic pop 

    } 

    char const* demangle(char const* const symbol) noexcept { 
     if (!symbol) { return "<null>"; } 
     std::lock_guard<std::mutex> lock(mangle_barrier); 
     int status = -4; 
     demangled_name.reset(
      abi::__cxa_demangle(symbol, 
           demangled_name.get(), 
           nullptr, &status)); 
     return ((status == 0) ? demangled_name.release() : symbol); 
    } 

} /* namespace terminator */ 

Để sử dụng điều này, tôi nghĩ bạn sẽ phải liên kết đến libc++ (hoặc bất kỳ nội dung nào tương đương tại địa phương của bạn) để sử dụng abi::__cxa_demangle(). Những gì có thể được tối ưu hóa cho OP là thực tế rằng điều này không demangling và stringification tại thời gian chạy. Cá nhân tôi yêu một cái gì đó là constexpr một cách thân thiện trong điều này, nhưng vì tôi bị dị ứng nặng do lạm dụng, tôi thấy đây là giải pháp ít phổ biến nhất cho vấn đề này.

(không gian tên terminator là vụn vặt - Tôi sử dụng mã này trong một stacktracer libunwind dựa trên gọi từ xử lý chấm dứt - cảm thấy tự do để s///g rằng token)

2

Nếu bạn sử dụng tăng/lõi/demangle.hpp, bạn có thể nhận được một chuỗi đáng tin cậy có thể đọc được.

char const * name = typeid(T).name(); 
boost::core::scoped_demangled_name demangled(name); 

std::cout << (demangled.get() ? demangled.get() : "Failed to demangle") << std::endl; 
0

trong mã của tôi, tôi sử dụng "khủng khiếp" đúp khai của "Class-Name"

MqFactoryC<MyServer>::Add("MyServer").Default(); 

vì C++ là không thể trích xuất chuỗi "myserver" từ mẫu ... "cách" duy nhất để có được "thoát" của điều này ... bằng cách sử dụng một "wrapper" cpp "

#define MQ_CPPSTR(s) #s 
#define MqFactoryCAdd(T) MqFactoryC<T>::Add(MQ_CPPSTR(T)).Default() 
Các vấn đề liên quan