stream parser

以前、stringstream で簡単 split という日記を書いたのですが、もうちょっと直感的な構文を使えないかと思い、実験してみました。

#include <iostream>
#include <string>
#include <sstream>

template <typename T>
struct string_to_any {
    void convert(const std::string& from, T& to) const {
	std::stringstream buf(from);
	buf >> to;
    }
};

template <>
struct string_to_any<std::string> {
    void convert(const std::string& from, std::string& to) const {
	if(!from.empty()) {
	    to = from;
	}
    }
};

template <typename T>
class stream_parser {
    T& val_;
    const std::string delimiter_;
    string_to_any<T> string_to_any_;

    std::string fetch(std::istream& in_file) const {
	std::string result;
	std::string tmp;
	char ch;

	while(tmp.size() < delimiter_.size() && in_file.get(ch)) {
	    if(ch == delimiter_[tmp.size()]) {
		tmp += ch;
	    } else {
		if(!tmp.empty()) {
		    result += tmp;
		    tmp.clear();
		}

		result += ch;
	    }
	}

	return result;
    }

public:
    stream_parser(T& val, const char* delimiter) : val_(val), delimiter_(delimiter) {}

    std::istream& execute(std::istream& in_file) const {
	string_to_any_.convert(fetch(in_file), val_);
	return in_file;
    }
};

template <typename T>
std::istream& operator>>(std::istream& in_file, const stream_parser<T>& parser) {
    return parser.execute(in_file);
}

template <typename T>
stream_parser<T> get(T& val, const char* delimiter) {
    return stream_parser<T>(val, delimiter);
}

int main() {
    std::string str = "default value";
    int num = -1;

    std::cin >> get(str, "::") >> get(num, "?n");
    std::cout << "str=" << str << ", num=" << num << std::endl;
}

うーん。この構文は微妙かなぁ。実行してみます。

$ ./a.out
stream parser test::12345
str=stream parser test, num=12345
$ ./a.out
::
str=default value, num=-1
$

文字列に空白も含めることができるし、空値の扱いも向上しているので、自前の設定ファイルをパースする程度ならまあ使えそうです。ただ、この構文を実現するためには、結構な呼び出し階層が必要になるんですね。マニピュレータとアプリケータの動きを勉強するには丁度良い課題でした。