この記事はQiitaのC++ Advent Calendar 2015の18日目の記事です。
ところで、ADVENTARのC++ Advent Calender 2015の記事ではありません。ADVENTARのC++カレンダーがいっぱいになった後、QiitaにもC++カレンダーが作られました。
ADVENTARへ投稿予定の記事が思ったより膨らんだため、分割して一部をこちらへ投稿させて頂きました。
#1.はじめに
私は、折角のC++をベターCとして長年使ってましたが、去年、今更ですがC++11規格の存在を知りC++の発展にびっくり。バリバリ使い始めて、多少は役に立ちそうな小技ツールをいくつか作ったのでご紹介させて頂きます。ちょっとソースが長いので1つです。
もし、お役に立てましたら、幸いです。
#2.TYPENAME()マクロ
テンプレート・メタ・プログラミングしていて、「この型は実際どんな型なんだ?」ってもどかしい時がよく有ります。
取り敢えずビルドまで成功すれば、typeid().name()を使えば確認できます。
しかし、この目的でtypeid().name()を使う時、問題が1つあります。typeid()はconstと参照を無視するのです。つまり、下記が全てtrueになってしまうのです。
#include <typeinfo>
#include <iostream>
int main()
{
std::cout << std::boolalpha;
std::cout << typeid(int)==typeid(int&) << "\n";
std::cout << typeid(int)==typeid(int const) << "\n";
std::cout << typeid(int)==typeid(int const&) << "\n";
return 0;
}
しかし、一旦、テンプレート・パラメータとして与えれば区別してされますので、下記は全てfalseになります。
#include <typeinfo>
#include <iostream>
template<typename tType>
struct Helper { };
int main()
{
std::cout << std::boolalpha;
std::cout << (typeid(Helper<int>) == typeid(Helper<int&>)) << "\n";
std::cout << (typeid(Helper<int>) == typeid(Helper<int const>)) << "\n";
std::cout << (typeid(Helper<int>) == typeid(Helper<int const&>)) << "\n";
return 0;
}
これを利用して、constと参照も区別できるようにしてみました。
また、gccではtypeid().name()はマングルされた名前を返却するので、読み取るのが難しいです。それではデバッグに使いづらいので、デマングルするようにしました。
#pragma once
// ***************************************************************************
// GCC用
// ***************************************************************************
#if defined(__GNUC__)
#include <cxxabi.h>
template<typename tType>
class GccTypeName
{
private:
char const* mName;
GccTypeName() : mName(0)
{
int status = 0;
mName = abi::__cxa_demangle(typeid(tType).name(), 0, 0, &status);
}
~GccTypeName() {delete mName;}
public:
static char const* get()
{
static GccTypeName instance;
return (instance.mName)?instance.mName:"\"demagle error\"";
}
// コピー/ムーブ禁止
GccTypeName(const GccTypeName&) = delete;
GccTypeName( GccTypeName&&) = delete;
GccTypeName& operator=(const GccTypeName&) = delete;
GccTypeName& operator=( GccTypeName&&) = delete;
};
template<typename tType>
char const* getTypeNameImpl()
{
return GccTypeName<tType>::get();
}
// ***************************************************************************
// その他のコンパイラ用
// ***************************************************************************
#else
template<typename tType>
char const* getTypeNameImpl()
{
return typeid(tType).name();
}
#endif
// ***************************************************************************
// 中継クラス
// ***************************************************************************
template<typename tType>
struct TypeName
{
static char const* get(bool aIsRough=false)
{
if (aIsRough) {
return getTypeNameImpl<tType>();
} else {
char const* wName=getTypeNameImpl<TypeName>();
char const* p;
for (p=wName; (*p != '<') && (*p != 0); ++p)
;
return (*p)?p:wName;
}
}
};
// ***************************************************************************
// マクロ
// VAR無しは、型を与える。
// VAR付きは、インスタンスを与える。
// ROUGH付きは、constと参照がついていてもを区別しない。
// ***************************************************************************
#define TYPENAME(dType) TypeName<dType>::get()
#define TYPENAME_VAR(dVar) TypeName<decltype(dVar)>::get()
#define TYPENAME_ROUGH(dType) TypeName<dType>::get(true)
#define TYPENAME_VAR_ROUGH(dVar) TypeName<decltype(dVar)>::get(true)
下記のようにして使います。
#include <iostream>
using namespace std;
#include "typename.h"
int main()
{
int wInt=0;
int& wIntRef=wInt;
int const wIntConst=wInt;
int const& wIntRefConst=wInt;
cout << "Rough\n";
cout << " " << TYPENAME_ROUGH(int) << "\n";
cout << " " << TYPENAME_ROUGH(int&) << "\n";
cout << " " << TYPENAME_ROUGH(int const) << "\n";
cout << " " << TYPENAME_ROUGH(int const&) << "\n";
cout << " " << TYPENAME_VAR_ROUGH(wInt) << "\n";
cout << " " << TYPENAME_VAR_ROUGH(wIntRef) << "\n";
cout << " " << TYPENAME_VAR_ROUGH(wIntRefConst) << "\n";
cout << "Exact\n";
cout << " " << TYPENAME(int) << "\n";
cout << " " << TYPENAME(int&) << "\n";
cout << " " << TYPENAME(int const) << "\n";
cout << " " << TYPENAME(int const&) << "\n";
cout << " " << TYPENAME_VAR(wInt) << "\n";
cout << " " << TYPENAME_VAR(wIntRef) << "\n";
cout << " " << TYPENAME_VAR(wIntConst) << "\n";
cout << " " << TYPENAME_VAR(wIntRefConst) << "\n";
return 0;
}
実行結果:
Rough
int
int
int
int
int
int
int
Exact
<int>
<int &>
<int const >
<int const &>
<int>
<int &>
<int const >
<int const &>
C++11対応が必要です。MSVC 2015とMinGW 4.9.2の-std=c++11オプション付きで動作確認しています。
#3.最後に
テンプレート・メタ・プログラミングってリフレクション的なこともでき、C++ではできないと思っていたことができるので、本当にびっくりです。
あの極限のメンテナンス性の悪さを正当化できる程の強力さですよね。
特にboostシリアライザは凄い。基底クラスへのポインタが指す派生クラスのインスタンスをシリアライズできます。そして、基底クラスのポインタへ、派生クラスのインスタンスを回復できるのですよ。
それもこれも、テンプレート・メタ・プログラミングを超駆使しているからですね。boostのシリアライザを見ていろんな意味でめまいがしましたぜ。(えええっ!そんなことができるのか!! どれどれ・・・ぐはっ、こ、このソース、読めね~~っ!!)
死にかけながら読みました。ポリモーフィックなシリアライズ/デシリアライズは、instantiate_ptr_serialization()が核でした。なんと!オーバーロード候補関数(選択された関数ではない、単なる候補)が返すクラスを実体化してました。何を言っているでしょうね?