関数テンプレートの特殊化について

関数テンプレートの特殊化いろいろ


なんか日記を書くのを忘れていました。よくない。というわけで、最近おぼえたtipsを書きます。クラステンプレート内のテンプレートの特殊化(もどき)についての自分なりのまとめ。


まず、

namespace A {
  template<int V>
  static void YYY() {
    std::printf("default %d\n", V);
  }
}
int main() {
  YYY<0>();
  YYY<128>();
}

のような関数テンプレートがあるとき、これを特殊化*1した関数は

namespace A {
  template<int V>
  static void YYY() {
    std::printf("default %d\n", V);
  }
  template<>
  static void YYY<128>() {
    std::printf("for 128\n");
  }
  template<>
  static void YYY<256>() {
    std::printf("for 256\n");
  }
}

のように書けます。同様に、メンバ関数テンプレート

struct B {
  template<int V>
  static void YYY() {
    std::printf("default %d\n", V);
  }
};

があった場合、この特殊化は、Bの中、クラススコープには規格上書いてはいけないことになっており、g++も実際にそういうコードを受け付けない*2ので、

template<>
inline void B::YYY<128>() {
  std::printf("for 128\n");
}
template<>
inline void B::YYY<256>() {
  std::printf("for 256\n");
}

のように名前空間スコープに書けばOKです。クラス外に出すとインライン化されにくくなるので、inlineをつけておきました。では、外側のクラスがクラステンプレートだった場合はどうかというと、

template <typename T>
struct C {
  template<int V>
  static void YYY() {
    std::printf("default %d\n", V);
  }
};

次のように、外側のCを明示的に特殊化した上で内側のYYY()を特殊化するような関数を名前空間スコープに書くことはできるけど、

// C<int> に対する YYY<128>()
template<> template<>
inline void C<int>::YYY<128>() {
  std::printf("for 128\n");
}

Cを特殊化しないでYYY()を特殊化するのはg++に "error: enclosing class templates are not explicitly specialized" だと言われてしまいコンパイルできません。C++の規格にも上のコードとそっくりの例まで挙げてダメだと書かれてます*3

// error: enclosing class templates are not explicitly specialized
template<typename T> template<>
inline void C<T>::YYY<128>() {
  std::printf("for 128\n");
}

そういうときどうするかというと、あまり詳しくないですがたぶん、古典ことMC++D, Modern C++ Designの2章に書いてある通り、

  • boost::type;
  • boost::mpl::bool_<B>
  • boost::mpl::int_
  • boost::mpl::integral_c

あたりのお好きなもの(LokiでいうType2Type, Int2Type)を使って、型とか個々の整数値、列挙値をそれ固有の軽い型のオブジェクトに変換して(説明になっていない)、オーバーロードで適切な関数を呼ぶようにすると思います。上の4つのMPL系クラステンプレートはそれぞれ3行ぐらいですので、自作するのも簡単です。

template <typename T>
class C { 
  void YYY_(boost::mpl::int_<128>) { /* specialized */ } 
  void YYY_(...) { /* default */ } 
public: 
  template<int V>
  static void YYY() {
    YYY_(boost::mpl::int_<V>());
  }
}; 

こうすると、YYY<0>(); と YYY<128>(); で別々の関数、それぞれ YYY_(...) と YYY_(boost::mpl::int_<128>) が呼ばれることになるので、特殊化が実現できたようなものです。これをここでは特殊化もどきと呼びます。


あるいは、次のように、クラススコープ、特に特殊化されていないクラステンプレートのスコープでも、クラステンプレートの部分特殊化なら可能であることを利用して、D::XXX()からD::D_::XXX()に処理を委譲してしまう手もあると思います。

template <typename T>
class D {
  template <int V, typename Dmy = void>
  struct D_ {
    static void XXX() {
      std::printf("default %d\n", V);
    }
  };
  template <int V> // partial specialization
  struct D_<V, typename boost::enable_if_c<V == 128>::type> {
    static void XXX() {
      std::printf("for %d\n", V);
    }
  };
  template <int V> // partial specialization
  struct D_<V, typename boost::enable_if_c<V == 256>::type> {
    static void XXX() {
      std::printf("for %d\n", V);
    }
  };
public:
  template<int V>
  static void XXX() {
    D_<V>::XXX();
  }
};

BoostのMLのこのメールでは、こちらの方法が推奨されていました。次の通り、D_ の部分特殊化版を誰でも後から追加できますからね。

// namespace scope に V==0の場合の特殊化を後から追加
template <typename T> template <int V>
struct D<T>::D_<V, typename boost::enable_if_c<V == 0>::type> {
  static void XXX() {
    std::printf("for %d\n", V); 
  }
};

のように。boost::enable_ifは、これまた数行で書けるだいたいこんな感じのクラステンプレートです。というわけで、「クラステンプレートC内のメンバ関数テンプレートを、Cを特殊化しないで特殊化(もどき)する」には、「クラステンプレートに委譲しとけば?」でファイナルアンサーのように思うのですが、練習(何の?)ため、他の関数に処理を委譲せずに特殊化もどきを実現できないかを考えてみます。

関数テンプレートの enable_ifオーバーロードによる特殊化もどき - 2分岐バージョン


もし、特殊化する関数が1つだけ、Pのときの関数と!Pのときの関数だけ用意すればことたりるなら、boost::enable_ifと関数のオーバーロードを使って、委譲なしで綺麗に特殊化もどきを実現できます。

  template<bool B>
  static void bar(typename boost::enable_if_c<B>::type* = 0) {
    std::printf("for true\n");
  }
  template<bool B>
  static void bar(typename boost::disable_if_c<B>::type* = 0) {
    std::printf("for false\n");
  }

としたり、

  template<int V>
  static void boo(typename boost::enable_if_c<V == 0>::type* = 0) {
    std::printf("for 0\n");
  }
  template<int V>
  static void boo(typename boost::enable_if_c<V != 0>::type* = 0) {
    std::printf("default\n");
  }

としたり、です。

(おまけ)関数テンプレートの enable_ifオーバーロードによる特殊化もどき - 3+分岐バージョン


ですが、合計3つ以上の関数を用意したい場合にはenable_ifはちょっと面倒です。

  template<int V>
  static void boo(typename boost::enable_if_c<V == 0>::type* = 0) {
    std::printf("for 0\n");
  }
  template<int V>
  static void boo(typename boost::enable_if_c<V == 1>::type* = 0) {
    std::printf("for 1\n");
  }
  static void boo(typename boost::enable_if_c<V == 2>::type* = 0) {
    std::printf("for 2\n");
  }
  static void boo(typename boost::enable_if_c<!(V == 0) && !(V == 1) && !(V == 2)>::type* = 0) {
    std::printf("default\n");
  }

のようにdefaultな関数の引数がえらいことになります。boo(...) などでdefault関数を書ければいいんですけどね。コンパイラに曖昧だと言われてしまってどうにも。どうにかなるでしょうか。


上の例は、特殊化(もどき)の条件(V==ほげ)が2回づつ登場してしまっているのが気に入りません。ですので、今回は、MPLを使って、頑張って特殊化もどきの条件を各1回だけ書けばいいようにしてみました。はじめてのMPLと。

#include <cstdio>
#include <boost/utility/enable_if.hpp>
#include <boost/type_traits/is_same.hpp>
#include <boost/mpl/int.hpp>
#include <boost/mpl/bool.hpp>
#include <boost/mpl/lambda.hpp>
#include <boost/mpl/list.hpp>
#include <boost/mpl/at.hpp>
#include <boost/mpl/or.hpp>
#include <boost/mpl/fold.hpp>

template <typename T>
struct X {

  struct detail {
    typedef boost::mpl::list<
      boost::mpl::lambda<
        boost::is_same<
          boost::mpl::_1,
          boost::mpl::int_<128>  // V==128で特殊化したい
        >
      >::type,
      boost::mpl::lambda<
        boost::is_same<
          boost::mpl::_1,
          boost::mpl::int_<256>  // V==256で特殊化したい
        >
      >::type
    > value_list;

    // boost::mpl::fold<> にあとで渡す
    template<int V>
    struct compar_meta_fn {
      template<typename Sum, typename Seq>
      struct apply {
        typedef boost::mpl::or_<
          Sum, /* true_ or false_ */
          typename Seq::type::template apply<boost::mpl::int_<V> >::type
        > type;
      };
    };
  };

  // ::template apply<... という奇妙な文法については、書籍 "C++ Templates - the complete guide" の9章に丁寧な解説があります
  template<int V>
  static void XXX(typename boost::enable_if<
                     typename boost::mpl::at_c<typename detail::value_list, 0>::type::template apply<boost::mpl::int_<V> >::type
                  >::type* = 0) {
    std::printf("for %d\n", V); // 128
  }

  template<int V>
  static void XXX(typename boost::enable_if<
                     typename boost::mpl::at_c<typename detail::value_list, 1>::type::template apply<boost::mpl::int_<V> >::type
                  >::type* = 0) {
    std::printf("for %d\n", V); // 256
  }

  template<int V>
  static void XXX(typename boost::disable_if<
                     typename boost::mpl::fold<
                       typename detail::value_list,
                       boost::mpl::false_,
                       typename boost::mpl::lambda<typename detail::template compar_meta_fn<V> >::type
                    >::type
                  >::type* = 0) {
    std::printf("default %d\n", V);
  }
};

int main() {
  X<int>::XXX<0>();
  X<long>::XXX<128>();
  X<float>::XXX<256>();
  X<double>::XXX<512>();
}

以上です。実行結果:

% ./a.out
default 0
for 128
for 256
default 512

整数値でなく型で特殊化もどきをしたい場合は、boost::mpl::int_ではなくboost::typeを使えばOKです。is_same<>も別のに変えた方がいいかもしれないけど。で、ちょっとこのままでは長すぎるので、適当にマクロで(BOOST.PPは見なかったことにします)

template <typename T> struct Z {
  LIST_BEGIN()
    LIST_ELEM(128),
    LIST_ELEM(256)
  LIST_END(lst);

  template<int V>
  static void XXX(ENABLE_NTH(lst,0)) {
    std::printf("for %d\n", V); // 128
  }
  template<int V>
  static void XXX(ENABLE_NTH(lst,1)) {
    std::printf("for %d\n", V); // 256
  }
  template<int V>
  static void XXX(OTHERWISE(lst)) {
    std::printf("default %d\n", V);
  }
};

くらいに縮めればなんとか使え...ないですかね。とりあえず、異なるVごとに別の XXX(OTHERWISE) 関数が生成されてしまうのは問題か。

結論


MPLを今更ながら使ってみたら面白かった。なお、MPLを使ってみるにあたっては、稲葉一浩さんのBoost本(第二版)に大変お世話になりました。

*1:関数テンプレートの部分的な特殊化はできないから、以下関数に付いて単に特殊化と言ったら明示的特殊化です

*2:VC++はなぜか書けるみたいだけど

*3:これまたVC++では通るみたいですが...。なんてプログラマに優しいんだ