ぼやきごと/2011-09-14/派生クラスに対するテンプレート特殊化の適用 のバックアップ差分(No.1)


  • 追加された行はこの色です。
  • 削除された行はこの色です。
#blog2navi()
*派生クラスに対するテンプレート特殊化の適用 [#gf8a0280]

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

#code(c){{
#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<T>
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;
}
}}

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

#pre{{
Normal
Special
Normal
}}

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

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

#code(c){{
#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<T>
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;
}
}}

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

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

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

#code(c){{
#include <boost/type_traits/is_base_of.hpp>
#include <boost/utility/enable_if.hpp>
#include <iostream>

//----------

class Base { };

class Derived : public Base { };

//----------

template<class T, class = void>
struct Func
{
    void operator()() const
    {
        std::cout << "Normal" << std::endl;
    }
};

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;
}
}}

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

-[[本の虫: Boostのenable_ifについて>http://cpplover.blogspot.com/2008/01/boostenableif.html]]
-[[Let's Boost - メタプログラミング>http://www.kmonos.net/alang/boost/metaprog.html]]

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

RIGHT:Category: &#x5b;[[C++>ぼやきごと/カテゴリ/C++]]&#x5d;&#x5b;[[boost>ぼやきごと/カテゴリ/boost]]&#x5d;&#x5b;[[プログラミング>ぼやきごと/カテゴリ/プログラミング]]&#x5d; - 2011-09-14 06:33:11
----
RIGHT:&blog2trackback();
#comment(above)
#blog2navi()