コンストラクタテンプレート

今日の話題はSFINAE(Substitution Failure Is Not An Error),およびSFINAEを活用する基礎ツールとなるboost::enable_ifについての話題です.以下ではSFINAEについての基本的な知識,およびboost::enable_ifについての知識を仮定します.
SFINAEについてはK.INABA氏の説明が詳しいのでそちらを参照してください.また,boost::enable_ifについては同じくK.INABA氏の『Boost C++ Libraryプログラミング』(asin:4798007862)や,公式ドキュメント(残念ながら日本語訳が見当たりません)を参照してください.
さて,まずは以下のようなクラスを考えます.

template
class C{
public:
  C(T const &val = T())
    : v(val){}

  C(C const &r)
    : v(r.v){}

private:
  T v;
};

さて,このclass Cに対して次のコードを試してみます.

C a;
C b(a); // コンパイルエラー!!

上のコードを書いた人の主張は恐らくこうでしょう.
「charはintへの暗黙の変換が効くのだから,CからCへも暗黙の変換が効いても良いはずだ.」
しかし,上のコードは通りません.CにはCを引数とするコピーコンストラクタは定義されていますが,Cを引数とするCのコンストラクタ(あるいはCからCへの暗黙の型変換)は定義されていないからです.
CとCというのは「あらゆる意味でまったく関連性のない別の型である」ということに今一度注意してください.
しかし一方でCのようなクラスに対して,上のような暗黙の型変換を提供したいこともあるでしょう.その場合,以下のようにコンストラクタのテンプレートを定義すれば良いことになります.

template
class C{
  template friend class C;

public:
  C(T const &val = T())
    : v(val){}

  C(C const &r)
    : v(r.v){}

  template
  C(C const &r)
    : v(r.v){}

private:
  T v;
};

3つ目のコンストラクタがCからCへの暗黙の型変換を提供します.
たとえば

C a;
C b(a);

と書けば,2行目のコードは3つのコンストラクタテンプレートにおいてOtherT = charとして実体化されたものを呼び出していることになります.
上のコードで一点だけ注意するべきことがあります.以下の部分です.

template friend class C;

これは"friend template(宣言)"と呼ばれるもので,意味としては「あらゆるtemplate引数Tに対して,classをfriendとする」というものです.これによって,たとえばclassはclassやclassなどあらゆるCをfriendとしてしまいます.
なぜこれが必要かというと,以下のコード

  template
  C(C const &r)
    : v(r.v){} // Cのprivateメンバvに直接アクセスしている

において,Cのprivateメンバvに直接アクセスする必要があるからです.
何度も言うようですがCとCは別の型です.従って,そのままではCはCのprivateメンバに直接アクセスできません.しかし,上のようにfriend templateを書いておけばCがCのprivateメンバvに直接アクセスできるようになるわけです.(上の例では,Cにおけるfrined templateのintによる実体化がこれを可能にしています.)
上のような簡単な例では,vに対する公開メンバ関数からアクセスすれば良いでしょうが,一般には上のようにfriend templateを宣言したほうが良い場合も多いでしょう.
ちなみに,上のコードに対して下のコードは当然通りません.

C a;
C b(a);

char*からintへの暗黙の型変換は許されていないため,コンストラクタテンプレートのv(r.v)という初期化子の記述の部分でエラーを引き起こすからです.