読者です 読者をやめる 読者になる 読者になる

想定する型

知人が C++ のテンプレートについて、こんな話題を持ち出してきた。 (実際にはチャットでのやりとりなので対話的なものだったが、ここではわかりやすくひとつの文にまとめた。)

コンテナの要素の型を利用したい場合に

template<class V, class A> V sum(const vector<V,A> &c) { /* 省略 */ }

という風に書けば要素の型 V を取り出して定義の中で使えるが、 vector だけでなく任意のコンテナについて同じように処理できる定義にしたい。

template<ほにゃらら> V sum(const T<V,A> &c) { /* 省略 */ }

といった感じのことを考えている。 可能だろうか?
value_type を用いて

template<class T> typename T::value_type sum(const T &c) { /* 省略 */ }

と書くしかないだろうか?

テンプレート・テンプレートを使えば簡単に解決できる。
具体的にはこう書く。

template<template <class,class> class T, class V, class A>
V sum(const T<V,A> &c) { /* 省略 */ }

STL の多くのクラスには value_type があるが、世間に溢れているテンプレートクラスの中にはそうでないものもあるので、知っておけば便利なケースも少なからずあるだろう。

しかし、ここで当初の目的である「任意のコンテナについて」ということを思い出すと value_type を使う方が良い理由がある。 「少なくとも value_type をもつ型」でなければマッチすらしないからだ。 もちろん、想定しない型だったならテンプレートを展開する段階のどこかでエラーは出るだろうが、ご存知の通りテンプレート絡みのエラーメッセージは不可解なものになりがちでなので、なるべく早い段階で検出できるにこしたことは無い。

そして「任意のコンテナ」といっても実際には STL のコンテナを対象にすれば充分なのだとしたらこんなテクニックで型を限定することが出来る。

#include <vector>
#include <list>
using namespace std;

template <class A> struct container_id {};

#define register_container(container) \
template <class A, class B> \
struct container_id<container<A,B> > { \
  typedef container<A, B> id; \
}

register_container(vector);
register_container(list);

template<class T>
typename container_id<T>::id::value_type sum(const T &c) {
  /* 省略 */
}

想定する使い方にマッチするように書くのは当然だし、それに意識が向きがちだが、マッチしてはいけない場合のことを忘れていはいけない。

もちろん、どの程度まで気を付けるかは場合によるが、実際にミスした場合くらいは定義に反映した方が良いかもしれない。

Document ID: 1e7cc60f06148e956b2198283179f2c1