さて、最初に頭に浮かぶのは、範囲インターフェース(怠惰と構成可能性のため)とトランザクションインターフェース(バックトラックのため)を組み合わせることです:
#include <iostream>
#include <stack>
#include <sstream>
struct transaction_failure {};
class transactional_istream_range {
std::istream& stream;
std::stack<std::streampos> states;
public:
transactional_istream_range(std::istream& stream)
: stream(stream) {}
// Transaction interface.
template<class R, class T>
R transaction(R(*body)(T&)) {
try {
begin();
R result = body(*this);
commit();
return result;
} catch (const transaction_failure&) {
rollback();
}
return R();
}
void begin() {
states.push(stream.tellg());
}
void commit() {
states.pop();
}
void rollback() {
stream.seekg(states.top());
states.pop();
}
// Range interface.
bool empty() const {
return stream.peek() == EOF && stream.eof();
}
char front() const {
return stream.peek();
}
void pop_front() const {
stream.ignore(1);
}
};
次に、トランザクション範囲で動作するテンプレート関数を簡単に記述できます。
#include <cctype>
template<class R>
std::string parse_integer(R& input) {
std::string result;
while (!input.empty()) {
if (std::isdigit(input.front())) {
result += input.front();
input.pop_front();
} else {
throw transaction_failure();
}
}
return result;
}
int main() {
std::istringstream stream("1234a");
typedef transactional_istream_range tir;
tir input(stream);
std::string result = input.transaction(parse_integer<tir>);
std::cout << "Result: " << result;
}
これは最初の概算です。おそらく、トランザクション関数に範囲のタイプを指定する必要があることを回避できます(つまり、parse_integer
ではなくparse_integer<...>
)。多くの種類のレイジーストリームとレイジーアルゴリズムを範囲形式で記述するのは非常に簡単です。
これを拡張することに関しては、トランザクション処理をパラメーター化して、ユーザー指定のコミットまたはロールバック関数を呼び出すか、各タイプのロールバックを個別に実装することができます。ミックスインを使用して、範囲インターフェイスをトランザクションインターフェイスから分離することも有益な場合があります。ただし、仮想関数に頼らなければ、今のところそれを行うための良い方法は考えられません。