web-dev-qa-db-ja.com

C ++テンプレート内の型の名前を取得

私はいくつかのテキストデータファイルを解析するためのいくつかのテンプレートクラスを書いているので、解析エラーの大部分はデータファイルのエラーが原因であると考えられます。アプリの読み込みに失敗した理由などの素敵なメッセージ何かのようなもの:

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

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

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

編集:Okこれは私が思いついた解決策です:

私は以下を含む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ファイルで)使用できます。

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

67
Fire Lancer

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()を呼び出す関数に変更し、それが受け入れられない場合にのみ特化することもできます。

35
Logan Capaldo

解決策は

typeid(T).name()

std :: type_info を返します。

60
Jesse Beder

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

cppreference.com の読み取り:

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

...

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

しかし、場合によっては、gccは正しい文字列を返しません。たとえば、私のマシンでは_-std=c++11_というgccがあり、テンプレート関数typeid(T).name()は_"j"_に対して_"unsigned int"_を返します。これは、マングルされた名前と呼ばれます。実際の型名を取得するには、 abi :: __ cxa_demangle() function(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;
}
_
40
Bunkar

Bunkarで述べたように、typeid(T).nameは実装定義です。

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

例えば:

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

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++ファイルに配置することもできます...

9
rhomu

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

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

そのまま置いておきます。それでも誰かがそれを必要とするなら、これを使うことができます:

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

これは、GETではなくタイプのみをチェックし、1タイプまたは2に対してのみです。

1
Xar

Pretty_nameが必要な場合、Logan Capaldoのソリューションは複雑なデータ構造を処理できません。REGISTER_PARSE_TYPE(map<int,int>)およびtypeid(map<int,int>).name()St3mapIiiSt4lessIiESaISt4pairIKiiEEEの結果を返します

unordered_mapまたはmapを使用した別の興味深い回答があります 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;
}
1
Voyager