C++でテンプレート引数を文字列化することは可能ですか?私はこれを試しました:
#define STRINGIFY(x) #x
template <typename T>
struct Stringify
{
Stringify()
{
cout<<STRINGIFY(T)<<endl;
}
};
int main()
{
Stringify<int> s;
}
しかし、私が得るのは「T」であり、「int」ではありません。テンプレートを解決する前に、プリプロセッサが起動するようです。
これを行う他の方法はありますか?
テンプレートの解決後に前処理を行う方法はありますか? (コンパイラはVC++)。
あなたは試すことができます
typeid(T).name()
編集:コメントに基づいて修正されました。
テンプレートマジックを使用できます。
#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に何かあります).
または、コメントで提案されている Matrin 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;
ただし、その特定の型に関する情報をさらに格納する必要がある場合は、クラステンプレートの方が適しています。
コードで使用するマクロの検索と展開を担当するプリプロセッサが言語自体を認識していないため、コードは機能しません。これは単なるテキストパーサーです。 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()は、文字列解析の曖昧さを犠牲にしてかなり良い仕事をします(私は知っています、それはひどく醜いです)。
考慮すべきいくつかのポイント:
__PRETTY_FUNCTION__
を異なる形式でフォーマットしている場合、文字列の一致が壊れる可能性があるため、修正する必要があります。これと同じ理由で、getTypeName()mightがデバッグに適していることにも警告します(それでも、おそらくそれには向いていないこともあります)が、 確かに悪い、悪い、およびテンプレート内の2つのタイプの比較などのその他の目的またはそのようなもの(私は知りません、ただ推測して誰かが考えるかもしれません。デバッグのみに使用し、リリースビルドでは優先的に呼び出さないでください(マクロを使用して無効にする)。これにより、__PRETTY_FUNCTION__
を使用せず、コンパイラーがその文字列を生成しなくなります。これらの欠点にもかかわらず、私はそれが確かに速いと言いたいです。同じタイプ名を2回検索する場合、名前を含むグローバルstd :: stringへの参照を選択するのにコストがかかります。また、以前に提案されたテンプレートの特殊化メソッドと比較して、テンプレート自体以外に宣言する必要があるものは何もないため、非常に使いやすくなっています。
いいえ、タイプを変数であるかのように操作することはできません。要素の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();
}
これは私のC++コード作成の主な原則の1つを壊します。テンプレート機能とプリプロセッサの両方で同時にトリックを使用することは避けてください。
テンプレートの理由と、テンプレートが言語に導入する気さささの理由の1つは、開発者をプリプロセッサの使用から引き離そうとする試みでした。両方を使用すると、テロリストが勝利します。
Boost/core/demangle.hppを使用すると、信頼できる人間が読める文字列を取得できます。
char const * name = typeid(T).name();
boost::core::scoped_demangled_name demangled( name );
std::cout << (demangled.get() ? demangled.get() : "Failed to demangle") << std::endl;
demangle()
関数(abi::__cxa_demangle()
の上に実装されています)をいくつかの便利なテンプレート関数オーバーロードnameof()
で呼び出します。文字列化または同じインスタンスが必要です。
かなりコンパクトなので、ここではそのすべての栄光を再現します。 _demangle.hh
_では、次のようになります。
_#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 */
_
…そして_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 */
_
これを使用するには、abi::__cxa_demangle()
を使用するために_libc++
_(またはローカルで同等のもの)にリンクする必要があると思います。 OPにとって最適ではない可能性があるのは、これが実行時に解読と文字列化を行うという事実です。私は個人的にはconstexpr
に優しいものが大好きですが、深刻なマクロ虐待アレルギーに苦しんでいるため、これがこの問題に対する最も一般的に不合理な解決策であることがわかりました。
(terminator
名前空間は重要ではありません–このコードは、終了ハンドラーから呼び出されるlibunwindベースのスタックトレーサーで使用しています–そのトークンを自由に_s///g
_してください)
私のコードでは、「クラス名」の「ひどい」二重宣言を使用しています
MqFactoryC<MyServer>::Add("MyServer").Default();
c ++はテンプレートから文字列「MyServer」を抽出できないためです。これを「取り除く」ための唯一の「方法」は、cppの「ラッパー」を使用します。
#define MQ_CPPSTR(s) #s
#define MqFactoryCAdd(T) MqFactoryC<T>::Add(MQ_CPPSTR(T)).Default()