みぃちゃんの頭の中はおもちゃ箱

略してみちゃばこ。泣いたり笑ったり

出力反復子が さすデータの かた

2017年06月30日 21時58分02秒 | IT・デジタル
C++でのコーディング。文字列のキーとともに文字列を格納するコンテナを定義しました。便利に つかっていましたが、取得した文字列をデータ列と みなして解釈し、そのデータ列を任意のコンテナに格納する機能を実装したいと おもいました。たとえば、キーを指定して取得した文字列を (コンマ区ぎりなどの) 数値列と みなして、それぞれの数値を任意の かた (intやdoubleなど) に変換してコンテナに格納する機能は、利用する機会も おおいでしょう。

データ列の かたは いろいろ かんがえられるので、外部からはget()メンバ関数をよびだし、そのget()メンバ関数が内部でデータ列の解釈方法を指定して別のメンバ関数をよびだすように構成します。
class A
{ public:
	...
	// 外部からはこの関数をよびだす。
	// データの かたに よって分岐させるためのヘルパ関数
	template <class OutIter>
	 OutIter get(const std::string &key, OutIter oi) const;

protected:
	template <typename T> struct StringParser;

	// 実際の処理では、上記のget()が以下のget()をよびだす
	template <class Parser, class OutIter>
	 OutIter get(const std::string &key, Parser parser, OutIter oi) const;
	...
}

// データ解釈用の関数オブジェクト群
template <typename T> struct A::StringParser
{
 T operator () (const std::string &s);
};

template <> struct A::StringParser<int>
{
 int operator () (const std::string &s) { return std::atoi(s.c_str()); }
};
template <> struct A::StringParser<long>
{
 long operator () (const std::string &s) { return std::atol(s.c_str()); }
};
// and so on...

template <class Parser, class OutIter>
 OutIter A::get(const std::string &key, Parser parser, OutIter oi) const
{
	while (...) {
		...
		// いろいろな処理
		...
		*out_to++ = parser( elem);
				// かたによる処理の ちがいをparserで吸収
	}
	return oi;
}

// クラス外からはこのget()をよびだす
template <class OutIter>
 OutIter A::get(const std::string &key, OutIter oi) const
{
	return get(key, StringParser< decltype(*oi) >(), oi);
}

void f(A &a)
{
	std::vector<int> v;
	a.get(key, std::back_inserter(v));
	...
}

でも、これは不正なコード。decltype(*oi)がintに なることを期待していますが、そうは なりません。back_inserter()で生成される反復子の かた (クラス) はback_insert_iterator<Ctn>ですが、back_insert_iterator<Ctn>::operator *()が かえす かたは そのクラスに対する参照 (back_insert_iterator<Ctn> &) です。decltype(*oi)はback_insert_iterator<Ctn> &になります。これに対応するStringParserは ありませんから、コンパイル エラーが でます。

関連記事: 2014年9月5日: だまされてる?

上記のA::get()を以下のように かきかえることも できません。
template< class OutIter>
 OutIter A::get(const std::string &key, OutIter oi) const
{
 return get(key, StringParser< typename std::iterator_traits<OutIter>::value_type >(), oi);
}

iterator_traits< back_insert_iterator<Ctn> >::value_typeはvoidなので、上記の第2パラメータはStringParser<void>()に なってしまいます。もちろんvoid用のStringParserは定義できないので (文字列からvoid値を取得するのは無意味ですし、データの解釈方法も判別できません)、この方法でも問題を解消できません。

たしかに出力反復子の もどりがたを一般的に定義することは できません。でも、ポインタと おなじ はたらきをする反復子など、特定の反復子に ついては正当な かたを定義できるのだから、これくらい できても いいんじゃないかとC++の仕様に文句をいうわけです。

しかたが ないので
template <typename T, class OutIter>
 OutIter A::get(const std::string &key, OutIter oi) const
{
	return get(key, StringParser<T>(), oi);
}

void f(A &a)
{
	std::vector<int> v;
	a.get< decltype(v)::value_type >(key, std::back_inserter(v));
	...
}

として しのぐことに しました。うーん。不格好だし、ながったらしいなぁ。

template <class Container>
 void A::get_push_back(const std::string &key, Container c) const
{
	return get( key,
		StringParser< typename Container::value_type >(),
		std::back_inserter( c));
}

void f(A &a)
{
	std::vector<int> v;
	a.get_push_back(key, v);
	...
}

結局、push_back専用のメンバ関数を用意することに。ようやく すっきりしました。ほかにpush_front版やinsert版も かんがえられますが、この関数はデータ列を先頭から解釈して順番にコンテナに格納することから、頻繁に つかうのはpush_backでしょう。ほかのバージョンは「不格好」版をつかうことにします。

※ この記事の本文からは漢字の訓を排除しています。