92

私はいくつかのテキストデータファイルを解析するためのいくつかのテンプレートクラスを書いています。そのため、解析エラーの大部分はデータファイルのエラーが原因である可能性があります。データファイルのエラーの大部分はプログラマーによって作成されていないため、必要です。アプリの読み込みに失敗した理由についての良いメッセージ。たとえば、次のようなものです。

example.txtの解析中にエラーが発生しました。[MySectiom] Keyの値( "notaninteger")は有効なintではありません

テンプレート関数に渡された引数とクラスのメンバー変数からファイル、セクション、キーの名前を計算できますが、テンプレート関数が変換しようとしているタイプの名前を取得する方法がわかりません。

私の現在のコードは次のようになりますが、単純な文字列などに特化しています。

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

データファイルがたくさんあるので、データファイルが使用する可能性のあるすべてのタイプに対して特定のオーバーロードを作成する必要はありません...

また、例外が発生しない限り実行時のオーバーヘッドが発生しないソリューションが必要です。つまり、このコードは何回も呼び出され、ロード時間はすでにいくらか長くなっているため、完全なコンパイル時のソリューションが必要です。

編集:わかりました、これは私が思いついた解決策です:

私はtypes.hに次のものが含まれています

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

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

次に、DEFINE_TYPE_NAMEマクロを使用して、処理する必要のあるタイプごとにcppファイルを作成できます(たとえば、最初にタイプを定義したcppファイルで)。

リンカは、どこかで定義されている限り、適切なテンプレートの特殊化を見つけることができます。そうでない場合は、リンカエラーをスローして、型を追加できます。

4

10 に答える 10

91

解決策は

typeid(T).name()

std::type_infoを返します。

于 2009-06-28T18:42:03.033 に答える
54

typeid(T).name()は実装定義であり、人間が読める文字列を保証するものではありません。

cppreference.comを読む:

タイプの名前を含む、実装で定義されたnullで終了する文字列を返します。保証はありません。特に、返される文字列は複数のタイプで同一であり、同じプログラムの呼び出し間で変更される可能性があります。

..。

gccやclangなどのコンパイラを使用すると、返された文字列をc ++ filt -tにパイプして、人間が読める形式に変換できます。

ただし、場合によっては、gccが正しい文字列を返さないことがあります。たとえば、私のマシンにはgcc whithが-std=c++11あり、内部テンプレート関数は。をtypeid(T).name()返します。それはいわゆるマングル名です。実際の型名を取得するには、 abi :: __ cxa_demangle()関数(gccのみ)を使用します。"j""unsigned int"

#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;
}
于 2013-10-01T18:59:26.660 に答える
44

Jesse Bederのソリューションがおそらく最良ですが、typeidが与える名前が気に入らない場合は(たとえば、gccが壊れた名前を与えると思います)、次のようなことができます。

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

そしてそれを次のように使用します

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

編集:

2つを組み合わせて、nameデフォルトで呼び出す関数に変更し、typeid(T).name()それが受け入れられない場合にのみ特化することもできます。

于 2009-06-28T19:21:29.710 に答える
20

Bunkarが述べたように、typeid(T).nameは実装によって定義されます。

この問題を回避するには、Boost.TypeIndexライブラリを使用できます。

例えば:

boost::typeindex::type_id<T>().pretty_name() // human readable
于 2015-02-26T09:22:37.387 に答える
14

Logan Capaldoの答えは正しいですが、毎回クラスを専門化する必要がないため、わずかに簡略化できます。次のように書くことができます。

// 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...

これにより、REGISTER_PARSE_TYPE命令をC++ファイルに入れることもできます...

于 2016-04-28T21:58:19.483 に答える
13

このトリックは他のいくつかの質問で言及されましたが、まだここにはありません。

すべての主要なコンパイラは、拡張機能として__PRETTY_FUNC__(GCC&Clang)/ (MSVC)をサポートしています。__FUNCSIG__

このようなテンプレートで使用する場合:

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

コンパイラに依存する形式で文字列を生成します。この形式には、特に。の名前が含まれていますT

例:foo<float>()戻り値:

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

これらの文字列から型名を簡単に解析できます。コンパイラが型の前後に挿入する「ジャンク」文字の数を把握する必要があります。

コンパイル時に完全に行うこともできます。


結果の名前は、コンパイラによってわずかに異なる場合があります。たとえば、GCCはデフォルトのテンプレート引数を省略し、MSVCはクラスの前に単語を付けclassます。


これが私が使用している実装です。すべてはコンパイル時に行われます。

使用例:

std::cout << TypeName<float>() << '\n';
std::cout << TypeName(1.2f); << '\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>();
}
于 2019-12-29T19:54:01.800 に答える
8

アンドレイの答えの言い換えとして:

Boost TypeIndexライブラリを使用して、タイプの名前を出力できます。

テンプレート内では、これは次のようになります。

#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;
}
于 2015-10-26T03:06:34.553 に答える
2

Pretty_nameが必要な場合、Logan Capaldoのソリューションは複雑なデータ構造を処理できません:REGISTER_PARSE_TYPE(map<int,int>) そしてtypeid(map<int,int>).name()私に次の結果を与えますSt3mapIiiSt4lessIiESaISt4pairIKiiEEE

unordered_maphttps://en.cppreference.com/w/cpp/types/type_indexを使用して、またはhttps://en.cppreference.com/w/cpp/types/type_indexmapから取得した別の興味深い回答があります。

#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;
}
于 2019-04-22T09:18:37.457 に答える
2

typeid(uint8_t).name()いいですが、「uint8_t」を期待しているときに「unsignedchar」を返します。

このコードは適切なタイプを返します

#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;
}
于 2020-02-14T13:35:18.530 に答える
0

そのままにしておきます。それでも誰かがそれを必要とする場合は、これを使用できます:

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

これは、タイプをチェックするだけで、取得するのではなく、1つのタイプまたは2つのタイプのみを対象とします。

于 2018-06-02T12:46:26.013 に答える