Home / ぼやきごと / 2011-09-14
2011-09-14

派生クラスに対するテンプレート特殊化の適用

次のC++コードを実行してみます。

すべて開くすべて閉じる
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 
 
-
!
-
!
 
-
!
 
-
!
-
!
 
-
|
-
|
!
!
 
-
!
 
-
|
-
|
!
!
 
-
!
 
-
|
|
|
|
|
!
#include <iostream>
 
//----------
 
//! Base クラス
class Base { };
 
//! Base クラスの派生クラス
class Derived : public Base { };
 
//----------
 
//! 関数オブジェクトテンプレート(基本型)
template<class T>
struct Func
{
    void operator()() const
    {
        std::cout << "Normal" << std::endl;
    }
};
 
//! Base クラス用に特殊化した Func
template<>
struct Func<Base>
{
    void operator()() const
    {
        std::cout << "Special" << std::endl;
    }
};
 
//----------
 
int main(int, char**)
{
    Func<int>     a; a();   // Normal を期待
    Func<Base>    b; b();   // Special を期待
    Func<Derived> c; c();   // Special を期待
 
    return 0;
}

main 関数のコメントにも書かれている通り、 Base クラスから派生した Derived クラスについてもテンプレートの特殊化が適用されて欲しいわけですが、出力は次のようになります。

Normal
Special
Normal

このように、あるクラスに対してのテンプレートの特殊化はその派生クラスには適用されません。

では Base クラスおよびその派生クラスについて特殊化したい場合はどうするのかという話ですが、例えば次のようにします。

すべて開くすべて閉じる
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 
 
-
!
-
!
 
-
!
 
-
!
-
!
 
-
|
|
|
|
|
|
|
|
|
|
-
!
!
 
-
!
-
!
 
-
|
-
|
!
!
 
-
!
 
-
|
-
|
!
!
 
-
!
 
-
|
|
|
|
|
!
#include <iostream>
 
//----------
 
//! Base クラス
class Base { };
 
//! Base クラスの派生クラス
class Derived : public Base { };
 
//----------
 
//! TDerived 型が TBase 型およびその派生型か否かのチェッククラス
template<class TBase, class TDerived>
class IsBaseOf
{
private:
    typedef char                  Yes;
    typedef struct { char v[2]; } No;
 
    static Yes check(const TBase&);
    static No  check(...);
 
    static TDerived d;
 
public:
    //! チェック結果
    static const bool value = (sizeof(check(d)) == sizeof(Yes));
};
 
//----------
 
//! 関数オブジェクトテンプレート(基本型)
template<class T, bool = IsBaseOf<Base, T>::value>
struct Func
{
    void operator()() const
    {
        std::cout << "Normal" << std::endl;
    }
};
 
//! Base クラスとその派生クラス用に部分特殊化した Func
template<class T>
struct Func<T, true>
{
    void operator()() const
    {
        std::cout << "Special" << std::endl;
    }
};
 
//----------
 
int main(int, char**)
{
    Func<int>     a; a();   // Normal を期待
    Func<Base>    b; b();   // Special を期待
    Func<Derived> c; c();   // Special を期待
 
    return 0;
}

IsBaseOf テンプレートクラスは、 TDerived 型が TBase 型およびその派生型であるか否かを静的メンバ定数 value から得ることのできるクラスです*1
オーバロードされている静的メンバ関数 check がミソで、 check(const TBase&) オーバロードを呼び出し可能であればそちらが、そうでなければ check(...) オーバロードが呼び出されます。
両者の戻り値の型の違いにより sizeof(check(d)) の値が変わり、それを sizeof(Yes) と比較することで TDerived 型が check(const TBase&) オーバロードを呼び出し可能な型であるか否かを調べています。

つまり IsBaseOf<Base, T>::value の値は、 T の型が Base クラスおよびその派生クラスであるか否かによって true もしくは false になります。
そしてその値が true であれば、テンプレートの部分特殊化が施された Func<T, true> がコンパイル時に生成される型として適用されるわけです。

…と、なんやかんや書きましたが、C++プラグラマにはおなじみの boost ライブラリを導入しているならばわざわざこんな風に書く必要はなく、次のように記述できます。
前述の例は汎用的に使おうとすると穴があったりするので、 boost ライブラリを導入済みなら迷わずこちらの書き方を用いましょう。

すべて開くすべて閉じる
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 
 
 
 
-
!
-
!
 
-
!
 
-
!
-
!
 
-
|
-
|
!
!
 
-
!
 
 
 
-
|
-
|
!
!
 
-
!
 
-
|
|
|
|
|
!
#include <boost/type_traits/is_base_of.hpp>
#include <boost/utility/enable_if.hpp>
#include <iostream>
 
//----------
 
//! Base クラス
class Base { };
 
//! Base クラスの派生クラス
class Derived : public Base { };
 
//----------
 
//! 関数オブジェクトテンプレート(基本型)
template<class T, class = void>
struct Func
{
    void operator()() const
    {
        std::cout << "Normal" << std::endl;
    }
};
 
//! Base クラスとその派生クラス用に部分特殊化した Func
template<class T>
struct Func<
    T,
    typename boost::enable_if< boost::is_base_of<Base, T> >::type>
{
    void operator()() const
    {
        std::cout << "Special" << std::endl;
    }
};
 
//----------
 
int main(int, char**)
{
    Func<int>     a; a();   // Normal を期待
    Func<Base>    b; b();   // Special を期待
    Func<Derived> c; c();   // Special を期待
 
    return 0;
}

上記コード中の boost::enable_if 等について詳しくは次のサイトが参考になります。

本記事では派生元がテンプレートクラスである場合について論じていませんが、それについてはまたいずれ。

Next: [テンプレートクラスとその派生クラスに対する特殊化の適用]
Category: [C++][boost][プログラミング] - 2011-09-14 06:33:11

*1 今回の例の場合、正確に言えば TDerived 型の変数が const TBase& 型に変換可能であるか否かを調べています。