LoginSignup
16
16

More than 5 years have passed since last update.

SFINAE で遊んでみた 1/2

Posted at
#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 コンテナ判定器.完成.";
16
16
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
16