コンテンツにスキップ

最終更新:2026-10-12

C++ 関数テンプレートよりクラステンプレート

https://stackoverflow.com/questions/5688355/partial-specialisation-of-member-function-with-non-type-parameter

最近、とても面白いテンプレートの問題を見かけました。関数テンプレートとクラステンプレートの基礎をどれだけ握っているか試される問題です。下の例では何が起こるでしょうか?

普通はどうやって直しますか?

template <typename T, int R>
struct foo {
  foo(const T& v) : value_(v) {}

  void bar() {
    std::cout << "Generic" << std::endl;
    for (int i = 0; i < R; ++i) std::cout << value_ << std::endl;
  }

  T value_;
};

template <>
void foo<float, 3>::bar() {
  std::cout << "Float" << std::endl;
  for (int i = 0; i < 3; ++i) std::cout << value_ << std::endl;
}

template <int R>
void foo<double, R>::bar() {
  std::cout << "Double" << std::endl;
  for (int i = 0; i < R; ++i) std::cout << value_ << std::endl;
}

答えは:最後の 2 つの特殊化はそのままコンパイルが通りません。

(1)1 つ目の template <> void foo<float, 3>::bar() は**完全特殊化**なので問題なし;

(2)2 つ目の template <int R> void foo<double, R>::bar() は**部分特殊化**をしようとしています——しかし関数テンプレートに部分特殊化は許されません! コンパイラはこれを新しい関数テンプレートのオーバーロードとして解析し、シグネチャが合わず、そのままエラーになります。

直し方は?クラステンプレートの登場

C++ のルールはこうです:関数テンプレートは完全特殊化しかできず、部分特殊化は不可能;クラステンプレートは両方可能。そこでイディオムとして、「型で分岐させたいロジック」を関数テンプレートから取り出してクラステンプレート(普通は trait/policy)に入れ、クラスに対して部分特殊化します:

template <typename T, int R>
struct bar_impl {
  static void run(foo<T, R>& f) {
    std::cout << "Generic" << std::endl;
    for (int i = 0; i < R; ++i) std::cout << f.value_ << std::endl;
  }
};

template <int R>
struct bar_impl<double, R> {  // 类模板偏特化:合法!
  static void run(foo<double, R>& f) {
    std::cout << "Double" << std::endl;
    for (int i = 0; i < R; ++i) std::cout << f.value_ << std::endl;
  }
};

template <typename T, int R>
void foo<T, R>::bar() { bar_impl<T, R>::run(*this); }

これは STL の定番の手口でもあります:iterator_traits、__type_traits、どれも「クラステンプレートが部分特殊化を担う」路線です。

まとめ

(1)関数テンプレート:オーバーロード+完全特殊化のみ、部分特殊化は不可;

(2)「特定の系統の型に特殊な実装を与えたい」ときは、実装をクラステンプレートに移し、部分特殊化/trait で振り分ける;

(3)C++11 以降は if constexpr で関数本体の中で直接分岐できる場面も多く、クラステンプレートすら省けます——ただし部分特殊化とのトレードオフは、あなたの考えに委ねます。

関連トピック

コメント