C ++ Lấy tên loại trong mẫu


78

Tôi đang viết một số lớp mẫu để phân tích cú pháp một số tệp dữ liệu văn bản và do đó, rất dễ xảy ra lỗi phân tích cú pháp sẽ do lỗi trong tệp dữ liệu, phần lớn không phải do lập trình viên viết, và vì vậy cần một thông báo hay về lý do tại sao ứng dụng không tải được, ví dụ như:

Lỗi phân tích cú pháp example.txt. Giá trị ("notaninteger") của [MySectiom] Key không phải là int hợp lệ

Tôi có thể tìm ra tệp, phần và tên khóa từ các đối số được truyền cho hàm mẫu và các vars thành viên trong lớp, tuy nhiên, tôi không chắc làm thế nào để lấy tên của loại mà hàm mẫu đang cố gắng chuyển đổi sang.

Mã hiện tại của tôi trông giống như thế, với các chuyên môn chỉ dành cho các chuỗi đơn giản và như vậy:

template<typename T> T GetValue(const std::wstring &section, const std::wstring &key)
{
    std::map<std::wstring, std::wstring>::iterator it = map[section].find(key);
    if(it == map[section].end())
        throw ItemDoesNotExist(file, section, key)
    else
    {
        try{return boost::lexical_cast<T>(it->second);}
        //needs to get the name from T somehow
        catch(...)throw ParseError(file, section, key, it->second, TypeName(T));
    }
}

Tôi thay vì phải tạo quá tải cụ thể cho mọi loại tệp dữ liệu có thể sử dụng, vì có rất nhiều chúng ...

Ngoài ra, tôi cần một giải pháp không phát sinh bất kỳ chi phí thời gian chạy nào trừ khi xảy ra ngoại lệ, tức là giải pháp thời gian biên dịch hoàn toàn là những gì tôi muốn vì mã này được gọi là tấn lần và thời gian tải đã hơi lâu.

CHỈNH SỬA: Ok, đây là giải pháp tôi đã đưa ra:

Tôi có một loại. H tiếp tục như sau

#pragma once
template<typename T> const wchar_t *GetTypeName();

#define DEFINE_TYPE_NAME(type, name) \
    template<>const wchar_t *GetTypeName<type>(){return name;}

Sau đó, tôi có thể sử dụng macro DEFINE_TYPE_NAME trong các tệp cpp cho từng loại tôi cần xử lý (ví dụ: trong tệp cpp đã xác định loại để bắt đầu).

Sau đó, trình liên kết có thể tìm thấy chuyên môn hóa mẫu phù hợp miễn là nó được xác định ở đâu đó hoặc nếu không thì trình liên kết sẽ gặp lỗi để tôi có thể thêm loại.


1
không thực sự liên quan đến câu hỏi của bạn, nhưng bạn có thể muốn sử dụng map.find (section) khi truy cập phần đó, trừ khi bạn cố ý muốn tạo một phần trống.
Idan K

Câu trả lời:


41

Giải pháp của Jesse Beder có thể là tốt nhất, nhưng nếu bạn không thích những cái tên mà typeid cung cấp cho bạn (tôi nghĩ gcc cung cấp cho bạn những cái tên bị xáo trộn chẳng hạn), bạn có thể làm như sau:

template<typename T>
struct TypeParseTraits;

#define REGISTER_PARSE_TYPE(X) template <> struct TypeParseTraits<X> \
    { static const char* name; } ; const char* TypeParseTraits<X>::name = #X


REGISTER_PARSE_TYPE(int);
REGISTER_PARSE_TYPE(double);
REGISTER_PARSE_TYPE(FooClass);
// etc...

Và sau đó sử dụng nó như

throw ParseError(TypeParseTraits<T>::name);

BIÊN TẬP:

Bạn cũng có thể kết hợp cả hai, thay đổi namethành một hàm mà theo mặc định gọi typeid(T).name()và sau đó chỉ chuyên biệt cho những trường hợp không được chấp nhận.


Lưu ý: Mã này sẽ không biên dịch nếu bạn quên xác định REGISTER_PARSE_TYPE cho loại mà bạn sử dụng. Tôi đã sử dụng một thủ thuật tương tự trước đây (trong mã không có RTTI) và nó đã hoạt động rất tốt.
Tom Leys

1
Tôi đã phải di chuyển tên bên ngoài cấu trúc trong g ++ 4.3.0 do "lỗi: khởi tạo trong lớp không hợp lệ của thành viên dữ liệu tĩnh của kiểu không tích phân 'const char *'"; và tất nhiên, từ khóa 'struct' là cần thiết giữa <> và TypeParseTraits và định nghĩa phải được kết thúc bằng dấu chấm phẩy.
mờTew 17-07-09

4
Chà, việc bỏ dấu chấm phẩy là có chủ ý, để buộc bạn phải sử dụng nó ở cuối lệnh gọi macro, nhưng cảm ơn bạn đã chỉnh sửa.
Logan Capaldo

Tôi gặp lỗi sau:error: '#' is not followed by a macro parameter
kratsg

@kratsg - đó là vì ở cuối '#x' phải là '#X' (chữ hoa để khớp với tham số macro) - Tôi sẽ sửa câu trả lời.
amdn

71

Giải pháp là

typeid(T).name()

trả về std :: type_info .


6
Hãy nhớ rằng việc trả về cùng một chuỗi cho mọi loại là tuân thủ (mặc dù tôi không nghĩ rằng bất kỳ trình biên dịch nào sẽ làm điều đó).
Motti

3
Hoặc để trả về một chuỗi khác cho cùng một kiểu trên các lần thực thi khác nhau ... (một lần nữa tôi không nghĩ rằng bất kỳ trình biên dịch lành mạnh nào cũng sẽ làm điều đó).
Emily L.

4
Tôi chỉ muốn chỉ ra cách xấu xí tên được có thể là: typeid(simd::double3x4).name() = "N4simd9double3x4E". typeid(simd::float4).name() = "Dv4_f"C ++ 17, Xcode 10.1.
Andreas phản đối sự kiểm duyệt

1
Thật. typeid(T).name()là cách chuẩn để thực hiện điều này, nhưng rất ít trình biên dịch trả về các tên không bị nhầm lẫn; người duy nhất mà cá nhân tôi quen thuộc làm như vậy là MSVC. Tùy thuộc vào trình biên dịch được sử dụng, cũng có khả năng nó có thể mất một số thông tin kiểu về các kiểu hàm, nhưng điều đó có thể không liên quan trong trường hợp này.
Justin Time - Phục hồi Monica vào

typeid(T).name()không trở lại std::type_info, nhưng char const *.
lmat - Phục hồi Monica

46

typeid(T).name() triển khai được xác định và không đảm bảo chuỗi có thể đọc được của con người.

Đọc cppreference.com :

Trả về một chuỗi ký tự kết thúc bằng rỗng được xác định bằng cách triển khai có chứa tên của kiểu. Không có đảm bảo nào được đưa ra, đặc biệt, chuỗi trả về có thể giống nhau đối với một số kiểu và thay đổi giữa các lệnh gọi của cùng một chương trình.

...

Với các trình biên dịch như gcc và clang, chuỗi trả về có thể được chuyển qua c ++ filt -t để được chuyển đổi sang dạng người có thể đọc được.

Nhưng trong một số trường hợp, gcc không trả về chuỗi bên phải. Ví dụ trên máy tính của tôi, tôi có gcc hộ -std=c++11và mẫu chức năng bên trong typeid(T).name()trở lại "j"cho "unsigned int". Nó được gọi là tên mangled. Để lấy tên kiểu thực, hãy sử dụng hàm abi :: __ cxa_demangle () (chỉ gcc):

#include <string>
#include <cstdlib>
#include <cxxabi.h>

template<typename T>
std::string type_name()
{
    int status;
    std::string tname = typeid(T).name();
    char *demangled_name = abi::__cxa_demangle(tname.c_str(), NULL, NULL, &status);
    if(status == 0) {
        tname = demangled_name;
        std::free(demangled_name);
    }   
    return tname;
}

1
Không phải là nó bị rò rỉ bộ nhớ để có freetrong if?
Tomáš Zato - Phục hồi Monica

2
Không, bởi vì các con trỏ trỏ đến nullptrnếu tình trạng không phải là 0.
Henry Schreiner

2
Tôi muốn nói thêm rằng có lẽ tốt nhất là bạn nên kiểm tra sự tồn tại của gcc hoặc clang và nếu không mặc định là không thực hiện gỡ tiếng như được hiển thị ở đây .
vị thần của lạc đà không bướu

20

Như đã đề cập bởi Bunkar typeid (T) .name được định nghĩa thực hiện.

Để tránh vấn đề này, bạn có thể sử dụng thư viện Boost.TypeIndex .

Ví dụ:

boost::typeindex::type_id<T>().pretty_name() // human readable

Điều này rất hữu ích để tìm ra tên kiểu mẫu khi các hàm được gọi. Nó hoạt động khá tốt cho tôi.
Fernando

1
Lưu ý rằng pretty_name () hoặc raw_name () vẫn được xác định triển khai. Trên MSVC cho một cấu trúc A; bạn sẽ nhận được: "struct A" trong khi trên gcc / clang: "A".
daminetreg

ồ. boostmột lần nữa để giành chiến thắng. tuyệt vời những gì thúc đẩy thực hiện mà không cần hỗ trợ trình biên dịch ( auto, regex, foreach, threads, static_assert, vv, vv ... hỗ trợ trước khi trình biên dịch / C ++ - Hỗ trợ tiêu chuẩn).
Trevor Boyd Smith

14

Câu trả lời của Logan Capaldo là đúng nhưng có thể được đơn giản hóa một chút vì không cần thiết phải chuyên biệt hóa lớp mỗi lần. Người ta có thể viết:

// in header
template<typename T>
struct TypeParseTraits
{ static const char* name; };

// in c-file
#define REGISTER_PARSE_TYPE(X) \
    template <> const char* TypeParseTraits<X>::name = #X

REGISTER_PARSE_TYPE(int);
REGISTER_PARSE_TYPE(double);
REGISTER_PARSE_TYPE(FooClass);
// etc...

Điều này cũng cho phép bạn đưa các hướng dẫn REGISTER_PARSE_TYPE vào tệp C ++ ...


8

Như một sự diễn đạt lại câu trả lời của Andrey:

Các TypeIndex Boost thư viện có thể được sử dụng để in tên của các loại.

Bên trong một mẫu, điều này có thể đọc như sau

#include <boost/type_index.hpp>
#include <iostream>

template<typename T>
void printNameOfType() {
    std::cout << "Type of T: " 
              << boost::typeindex::type_id<T>().pretty_name() 
              << std::endl;
}

6

Thủ thuật này đã được đề cập dưới một số câu hỏi khác, nhưng chưa có ở đây.

Tất cả các trình biên dịch chính đều hỗ trợ __PRETTY_FUNC__(GCC & Clang) / __FUNCSIG__(MSVC) dưới dạng phần mở rộng.

Khi được sử dụng trong một mẫu như thế này:

template <typename T> const char *foo()
{
    #ifdef _MSC_VER
    return __FUNCSIG__;
    #else
    return __PRETTY_FUNCTION__;
    #endif
}

Nó tạo ra các chuỗi ở định dạng phụ thuộc vào trình biên dịch, có chứa tên của, trong số những thứ khác T.

Ví dụ: foo<float>()trả lại:

  • "const char* foo() [with T = float]" trên GCC
  • "const char *foo() [T = float]" trên Clang
  • "const char *__cdecl foo<float>(void)" trên MSVC

Bạn có thể dễ dàng phân tích cú pháp tên loại từ các chuỗi đó. Bạn chỉ cần tìm ra bao nhiêu ký tự 'rác' mà trình biên dịch của bạn chèn vào trước và sau loại.

Bạn thậm chí có thể làm điều đó hoàn toàn tại thời điểm biên dịch.


Các tên kết quả có thể hơi khác nhau giữa các trình biên dịch khác nhau. Ví dụ: GCC bỏ qua đối số mẫu mặc định và tiền tố MSVC với từ này class.


Đây là một triển khai mà tôi đã sử dụng. Mọi thứ được thực hiện tại thời điểm biên dịch.

Ví dụ sử dụng:

std::cout << TypeName<float>() << '\n';
std::cout << TypeName(1.2f); << '\n';

Thực hiện:

#include <array>
#include <cstddef>

namespace impl
{
    template <typename T>
    constexpr const auto &RawTypeName()
    {
        #ifdef _MSC_VER
        return __FUNCSIG__;
        #else
        return __PRETTY_FUNCTION__;
        #endif
    }

    struct RawTypeNameFormat
    {
        std::size_t leading_junk = 0, trailing_junk = 0;
    };

    // Returns `false` on failure.
    inline constexpr bool GetRawTypeNameFormat(RawTypeNameFormat *format)
    {
        const auto &str = RawTypeName<int>();
        for (std::size_t i = 0;; i++)
        {
            if (str[i] == 'i' && str[i+1] == 'n' && str[i+2] == 't')
            {
                if (format)
                {
                    format->leading_junk = i;
                    format->trailing_junk = sizeof(str)-i-3-1; // `3` is the length of "int", `1` is the space for the null terminator.
                }
                return true;
            }
        }
        return false;
    }

    inline static constexpr RawTypeNameFormat format =
    []{
        static_assert(GetRawTypeNameFormat(nullptr), "Unable to figure out how to generate type names on this compiler.");
        RawTypeNameFormat format;
        GetRawTypeNameFormat(&format);
        return format;
    }();
}

// Returns the type name in a `std::array<char, N>` (null-terminated).
template <typename T>
[[nodiscard]] constexpr auto CexprTypeName()
{
    constexpr std::size_t len = sizeof(impl::RawTypeName<T>()) - impl::format.leading_junk - impl::format.trailing_junk;
    std::array<char, len> name{};
    for (std::size_t i = 0; i < len-1; i++)
        name[i] = impl::RawTypeName<T>()[i + impl::format.leading_junk];
    return name;
}

template <typename T>
[[nodiscard]] const char *TypeName()
{
    static constexpr auto name = CexprTypeName<T>();
    return name.data();
}
template <typename T>
[[nodiscard]] const char *TypeName(const T &)
{
    return TypeName<T>();
}

2

Nếu bạn muốn có một pretty_name, giải pháp của Logan Capaldo không thể xử lý cấu trúc dữ liệu phức tạp: REGISTER_PARSE_TYPE(map<int,int>)typeid(map<int,int>).name()cho tôi kết quả làSt3mapIiiSt4lessIiESaISt4pairIKiiEEE

Có một câu trả lời thú vị khác bằng cách sử dụng unordered_maphoặc mapđến từ https://en.cppreference.com/w/cpp/types/type_index .

#include <iostream>
#include <unordered_map>
#include <map>
#include <typeindex>
using namespace std;
unordered_map<type_index,string> types_map_;

int main(){
    types_map_[typeid(int)]="int";
    types_map_[typeid(float)]="float";
    types_map_[typeid(map<int,int>)]="map<int,int>";

    map<int,int> mp;
    cout<<types_map_[typeid(map<int,int>)]<<endl;
    cout<<types_map_[typeid(mp)]<<endl;
    return 0;
}

2

typeid(uint8_t).name() rất hay, nhưng nó trả về "unsigned char" trong khi bạn có thể mong đợi "uint8_t".

Đoạn mã này sẽ trả về cho bạn loại thích hợp

#define DECLARE_SET_FORMAT_FOR(type) \
    if ( typeid(type) == typeid(T) ) \
        formatStr = #type;

template<typename T>
static std::string GetFormatName()
{
    std::string formatStr;

    DECLARE_SET_FORMAT_FOR( uint8_t ) 
    DECLARE_SET_FORMAT_FOR( int8_t ) 

    DECLARE_SET_FORMAT_FOR( uint16_t )
    DECLARE_SET_FORMAT_FOR( int16_t )

    DECLARE_SET_FORMAT_FOR( uint32_t )
    DECLARE_SET_FORMAT_FOR( int32_t )

    DECLARE_SET_FORMAT_FOR( float )

    // .. to be exptended with other standard types you want to be displayed smartly

    if ( formatStr.empty() )
    {
        assert( false );
        formatStr = typeid(T).name();
    }

    return formatStr;
}

Điều này là tuyệt vời, nhưng tại sao không làm return #type;thay thế?
Little Helper

@LittleHelper: Bạn là đúng, mà có thể còn làm việc ...
jpo38

1

Tôi chỉ để nó ở đó. Nếu ai đó vẫn cần nó, thì bạn có thể sử dụng cái này:

template <class T>
bool isString(T* t) { return false;  } // normal case returns false

template <>
bool isString(char* t) { return true; }  // but for char* or String.c_str() returns true
.
.
.

Điều này sẽ chỉ KIỂM TRA loại không GET nó và chỉ cho 1 loại hoặc 2.

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.