46

C++でテンプレート引数を文字列化することは可能ですか? 私はこれを試しました:

#include <iostream>
#define STRINGIFY(x) #x
 
template <typename T>
struct Stringify
{
     Stringify()
     {
          std::cout << STRINGIFY(T) << endl;
     }
};
 
int main() 
{
     Stringify<int> s;
}

しかし、私が得るのは でありT、 ではありませんint。プリプロセッサ マクロは、テンプレートのインスタンス化の前に評価されるようです。

これを行う他の方法はありますか?

テンプレートのインスタンス化後に前処理を行う方法はありますか? (コンパイラはVC++です)。

4

8 に答える 8

38

あなたは試すことができます

 typeid(T).name()

編集:コメントに基づいて修正。

于 2009-09-28T17:07:13.663 に答える
27

テンプレートマジックを使用できます。

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

これには、RTTI (つまり ) よりも利点がありtypeinfoます。コンパイル中に解決されます。不利な点 - 自分で型情報を提供する必要があります (私が気付いていないことを既に行っているライブラリがある場合を除きます。おそらく Boost の何かでもあります)。

または、Martin Yorkがコメントで提案したように、代わりにインライン関数テンプレートを使用します。

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;

ただし、その特定の型に関するより多くの情報を保存する必要がある場合は、おそらくクラス テンプレートの方が適しています。

于 2009-09-28T17:16:05.820 に答える
18

コードで使用するマクロの検索と展開を担当するプリプロセッサが言語自体を認識していないため、コードが機能しません。それは単なるテキストパーサーです。関数テンプレートにある STRINGIFY(T) を見つけて、そのテンプレートに型を与えるずっと前に展開します。結局のところ、残念ながら、期待した型名ではなく、常に "T" が返されます。

litbが示唆したように、渡した型名を返すこの「getTypeName」関数テンプレートを (ひどく) 実装しました。

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

上記のコードは、GCC フラグ -s (「バイナリからすべてのシンボルを削除する」) を有効にして、次の出力を生成します。

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

つまり、 getTypename() はかなり優れた仕事をしますが、その厄介な文字列解析ハックを犠牲にしています (私は知っています、それは非常に醜いです)。

考慮すべきいくつかの点:

  • コードは GCC のみです。別のコンパイラに移植する方法がわかりません。おそらく、非常にきれいな関数名を生成する機能を備えているものは他にほとんどありません。私が検索したところ、MSVC++ には機能がありません。
  • 新しいバージョンで GCC フォーマット__PRETTY_FUNCTION__が異なる場合、文字列の一致が壊れる可能性があるため、修正する必要があります。これと同じ理由で、 getTypeName()はデバッグに適している可能性があることも警告します (それでも、デバッグは適していない可能性さえあります)。またはそのようなもの(わかりません。誰かがどう思うかを推測するだけです..)。デバッグのためにのみ使用し、優先的にリリース ビルドでは呼び出さないでください (マクロを使用して無効にします)。これにより、使用__PRETTY_FUNCTION__しないため、コンパイラはその文字列を生成しません。
  • 私は間違いなく専門家ではありません。奇妙な型が原因で文字列の照合が失敗する可能性があるかどうかはわかりません。この記事を読んでいる方で、そのような事例をご存知の方がいらっしゃいましたらコメントをお願いしたいです。
  • コードは静的 std::string を使用します。これは、何らかの例外がコンストラクターまたはデストラクターからスローされた場合、それが catch ブロックに到達する方法がなく、ハンドルされない例外が発生することを意味します。std::strings がそれを実行できるかどうかはわかりませんが、実行すると問題が発生する可能性があることに注意してください。メモリを解放するにはデストラクタが必要なので使用しました。ただし、そのために独自のクラスを実装して、割り当ての失敗以外に例外がスローされないようにし (これはかなり致命的ですよね? そうです...)、単純な C 文字列を返すことができます。
  • typedef を使用すると、次のような奇妙な結果が得られる場合があります (何らかの理由で、サイトがこのスニペットの書式設定を壊しているため、この貼り付けリンクを使用しています): http://pastebin.com/f51b888ad

これらの欠点にもかかわらず、確かに速いと言いたいです。同じ型名を 2 回目に検索すると、その名前を含むグローバルな std::string への参照を選択する必要があります。また、以前に提案されたテンプレートの特殊化メソッドと比較して、テンプレート自体以外に宣言する必要があるものは何もないため、非常に使いやすくなっています。

于 2009-09-28T17:23:15.507 に答える
12

いいえ、変数であるかのように型を処理することはできません。要素の typeid() を抽出して名前を出力するコードを書くこともできますが、結果の値はおそらく期待したものとは異なるでしょう (型名は標準化されていません)。

操作したいタイプの数が限られている場合は、テンプレートの特殊化 (およびいくつかのマクロ マジック) を使用して、より興味深いバージョンを実現することもできます。

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
}

または、両方のバージョンを組み合わせることもできます。typeinfo を使用して printtype ジェネリック テンプレートを実装し、より洗練された名前を付けたい型に特殊化を提供します。

template <typename T>
const char* printtype()
{
   return typeid(T).name();
}
于 2009-09-28T17:09:02.527 に答える
4

これは、C++ コードの記述に関する私の主な信条の 1 つを破るものです。つまり、テンプレート機能とプリプロセッサの両方で同時にトリックを使用することは避けてください。

テンプレートとそれが言語に導入する厄介さの理由の一部は、開発者をプリプロセッサの使用から遠ざける試みでした。両方を使用すると、テロリストが勝ちます。

于 2009-09-28T17:43:51.420 に答える
3

私のコードでは、「クラス名」の「ひどい」二重宣言を使用しています

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

c++ はテンプレートから文字列「MyServer」を抽出できないため…これを「取り除く」唯一の「方法」は…cpp「ラッパー」を使用することです。

#define MQ_CPPSTR(s) #s
#define MqFactoryCAdd(T) MqFactoryC<T>::Add(MQ_CPPSTR(T)).Default()
于 2018-02-27T09:30:22.893 に答える