SFINAE で遊んでみた 1/2

  • 17
    Like
  • 0
    Comment
More than 1 year has passed since last update.
#include <stdio.h>
#include <vector>
#include <map>
using namespace std;

// 準備中です...
int main() { return 0; }
struct Out {
  Out() {}
  Out(const Out& out) { puts(""); }
  Out& operator|(const char* mes) { puts(mes); return *this; }
  Out& operator|(int n) { printf("# %d  // ", n); return *this; }
};
template<int> struct Holder { static Out out; };
#define O template<> Out Holder<__LINE__>::out = Out()
#define V(...) __VA_ARGS__ | #__VA_ARGS__

O | "C++ の SFINAE という仕様を利用したトリックが"
  | "思った以上にパワフルだったので遊んでみました.";

O | "SFINAE とは,特定条件下でテンプレート置き換えの結果がエラーになっても,"
  | "それをコンパイルエラーとせず,単にそのテンプレートを無視するという"
  | "C++ の特徴です.";

O | "特定の型引数でエラーになるようなテンプレート A を書いても"
  | "SFINAE によって,コンパイルエラーにならず別のテンプレート B に"
  | "フォールバックさせることができるので,"
  | "エラーの有無で A B を切り替えるような挙動がコンパイル時に可能です.";

O | "★ SFINAE を使わない判定器";

O | "まず,value 定数を持つ2つの型を用意します.";

struct true_v {
  enum { value = 1 };
};

struct false_v {
  enum { value = 0 };
};

O | "次に is_void テンプレートを用意します."
  | "デフォルトの実装では false_v を継承します.";

template <class T>
struct is_void : public false_v {};

O | "false_v を継承しているので,is_void<XXX>::value は 0 ですね."
  | "確認してみましょう";

O | V(is_void<double>::value);

O | "is_void はテンプレートなので"
  | "特定のテンプレート引数に対して特殊化できます."
  | "つまり…";

template <>
struct is_void<void> : public true_v {};

O | "特殊化することで与えられた型引数が void の時だけ true_v を継承し,"
  | "value が 1 を返すような判定器になりました."
  | "確認してみましょう.";

O | V(is_void<char>::value)
  | V(is_void<long>::value)
  | V(is_void<void>::value);

O | "これは普通のテンプレート特殊化で,SFINAE は使っていません.";

O | "★ SFINAE を使う判定器";

O | "次は有名な STL コンテナ型の判定器を作ってみます."
  | "この判定器はある型 T が T::iterator を持つか否かで"
  | "その型が STL コンテナであるか否かを判定します."
  | "実際には型 T が STL コンテナであるためには,"
  | "もっと厳しい条件がつくそうなので,これは簡易判定器です.";

O | "先ほどと同じように特殊化前のテンプレートを false_v を継承して用意します.";

template <class T, class X=void>
struct is_stl : public false_v {};

O | "is_void と異なり is の型引数に X が加わわっています."
  | "特殊化する時に,この X に T::iterator を含むか否かを"
  | "コンパイラに判断させる式をあてます.";

O | "次にヘルパーテンプレートを用意します.";

template <class>
struct ignore {
  typedef void type;
};

O | "このテンプレートは次のように type を参照するために使います."
  | "テンプレート引数が宣言内でまったく使われていないので,"
  | "どんな引数を渡しても type は void になります."
  | "引数を無視するという意味で ignore と名づけました."
  | "本当に void になっているのか,先ほどの is_void で"
  | "確認してみましょう.";

O | V(is_void< ignore<void>::type >::value)
  | V(is_void< ignore<int>::type >::value)
  | V(is_void< ignore< vector<int> >::type >::value);

O | "どうやらきちんと,void になっているようです."
  | "最後にこのヘルパーテンプレートを使って, is_stl を特殊化します.";

template <class T>
struct is_stl<T, typename ignore<typename T::iterator>::type> :
    public true_v {};

O | "第一引数はテンプレート T がそのままあてられていて特殊化されていません."
  | "第二引数は ignore<XXX>::type があてられているのでコイツは void です."
  | "つまりこの特殊化は,第二引数が void である場合に適用される"
  | "ということになります.";

O | V(is_stl< vector<int>, int >::value)
  | V(is_stl< vector<int>, void >::value)
  | V(is_stl< map<int, int>, void >::value);

O | "また第二引数のデフォルトは void なので省略した場合も特殊化が行われます.";

O | V(is_stl< vector<int> >::value)
  | V(is_stl< map<int, int> >::value);

O | "では 第一引数はなんでも良いのかというとそうではなく"
  | "テンプレートの特殊化で T::iterator という記述がされているので"
  | "iterator が存在しないような型,"
  | "例えば int が T として渡されると,int::iterator エラーになります."
  | "T::iterator は ignore テンプレートによって無視されてしまう部分ですが,"
  | "コンパイラはきちんと解釈を行なっているのです.";

O | "ここでようやく SFINAE の登場です."
  | "通常 int::iterator など存在しない識別子を参照した場合,"
  | "これはコンパイラエラーになってしまいます.";

O | "しかしテンプレートの置き換えによって生じた今回のエラーは話が別で,"
  | "SFINAE によってコンパイルエラーとしては扱われず,単にこの特殊化が"
  | "無視されるだけですみます.";

O | "結果として T が iterator を持たない場合は特殊化の宣言がなくなり,"
  | "元の宣言が使われることになります.";

O | V(is_stl< vector<int> >::value)
  | V(is_stl< map<int, int> >::value)
  | V(is_stl< int >::value)
  | V(is_stl< double >::value);

O | "実はこのテンプレートは第二引数を常に省略して使うので,"
  | "T::iterator がエラーになるかどうかが,特殊化の成否を分けます."
  | "STL コンテナ判定器.完成.";