メンバ関数の部分特殊化がしたい

C++メンバ関数を部分特殊化しようとすると(例えばテンプレートクラスのテンプレートメンバ関数を,ある特定の型について特殊化する),コンパイルエラーとなる.gcc なら "error: invalid use of incomplete type" だとか, "error: enclosing class templates are not explicitly specialized" などのエラーが発生する.しかし,現実には,部分特殊化したいことも多い.そこで,やや強引な解決策を実装してみた.

問題提起

template <typename T1, typename T2>
struct TTest
{
  T1 ex()
    {
      ...
    }
};
template <>
void TTest<void, void>::ex()  // OK
{
  ...
}

これは,メンバ関数 TTest::ex を T1=void, T2=void について(完全に)特殊化している例で,ちゃんとコンパイルできる.これに対し,

template <typename T1, typename T2>
struct TTest
{
  T1 ex()
    {
      ...
    }
};
template <typename T2>
void TTest<void, T2>::ex()  // NG
{
  ...
}

このように,メンバ関数 TTest::ex を T1=void のみについて部分特殊化すると(i.e. T2 についてはテンプレートのまま),コンパイルエラーとなる. gcc の場合, "error: invalid use of incomplete type 'struct TTest'" というエラーだ.

解決

クラス TTest 内にテンプレート関数オブジェクトを作る.部分特殊化したい関数の代わりに,このテンプレート関数オブジェクトを特殊化しておき,部分特殊化したい関数からは,このテンプレート関数オブジェクトを呼び出す.ただし,テンプレート関数オブジェクトの完全な特殊化を C++ は許していないから,ダミーテンプレート引数を設けることで対処する.以上をまとめると,

template <typename T1, typename T2>
struct TTest
{
  T1 ex()
    {
      // 特殊化したい関数からは,関数オブジェクト dummy_ex を呼び出す
      return dummy_ex<T1>()();
    }

  // 通常の型 T1 に対する実装:
  template <typename TX, typename dummy_type=void>
  struct dummy_ex
    {
      TX operator()()
        {
          ...
        }
    };
  // dummy_ex を void について特殊化し, T1==void に対して実装する:
  template <typename TX>
  struct dummy_ex <void, TX>
    {
      void operator()()
        {
          ...
        }
    };
};

ダミーテンプレート引数 dummy_type を持たせない場合, dummy_ex の特殊化は完全な特殊化になってしまい,"error: explicit specialization in non-namespace scope 'struct TTest'" などというエラーを吐く.

クラス外部で実装する場合は,

template <typename T1, typename T2>
struct TTest
{
  T1 ex()
    {
      return dummy_ex<T1>()();
    }

  template <typename TX, typename dummy_type=void>
  struct dummy_ex;
};

// 通常の型 T1 に対する実装:
template <typename T1, typename T2>
template <typename TX, typename dummy_type>
struct TTest<T1,T2>::dummy_ex
{
  TX operator()()
    {
      ...
    }
};

// dummy_ex を void について特殊化し, T1==void に対して実装する:
template <typename T1, typename T2>
template <typename TX>
struct TTest<T1,T2>::dummy_ex< void, TX >
{
  void operator()()
    {
      ...
    }
};

のように書けばよい.この場合も, dummy_type を除去すると,"error: enclosing class templates are not explicitly specialized" というエラーが発生し,コンパイルできない.

少し脱線: 参照型,ポインタ型への特殊化

dummy_ex を TX& とか TX* に特殊化する場合,部分特殊化となるため dummy_type は不要となる.例えばポインタ型への特殊化は,

template <typename T1, typename T2>
struct TTest
{
  T1 ex()
    {
      return dummy_ex<T1>()();
    }

  template <typename TX>
  struct dummy_ex
    {
      TX operator()()
        {
          ...
        }
    };
  // ポインタ型に対する部分特殊化
  template <typename TX>
  struct dummy_ex<TX*>
    {
      TX* operator()()
        {
          ...
        }
    };
};

のような感じになる.

サンプル

簡単なサンプルコードをひとつ.

#include <iostream>
using namespace std;

template <typename T1, typename T2>
struct TTest
{
  T1 ex()
    {
      return dummy_ex<T1>()();
    }

  template <typename TX, typename dummy_type=void>
  struct dummy_ex
    {
      TX operator()()
        {
          TX tmp(1);
          cout<<"default member function"<<endl;
          return tmp;
        }
    };
  template <typename TX>
  struct dummy_ex< void, TX >
    {
      void operator()()
        {
          cout<<"member function specialized to void"<<endl;
          return;
        }
    };
};

int main(int argc, char**argv)
{
  TTest<int,int> test1;
  TTest<void,int> test2;
  test1.ex();
  test2.ex();
  return 0;
}

g++ に "-ansi -pedantic -Wall" (厳格なANSI準拠モード)などをつけてコンパイルした.