C++でJavaScriptのString.prototype.replaceを実現できるか
ECMA ScriptのString.prototype.replaceメソッドは第二引数が関数だった場合、パターンマッチでキャプチャされる部分が可変長でしかもこの可変長引数が固定引数に挟まれているという不思議仕様です。
使用例:
#!/usr/bin/env node console.log( "(f)ooba(r)".replace(function(part, p1, p2, pos, src) { console.log( Array.prototype.slice.call(arguments) ); }); // 結果: // [ 'oba', 'o', 'a', 2, 'foobar' ]
なぜString.prototype.matchが返すようなオブジェクトにしなかったのかと問い詰めたくなります。おそらく、パフォーマンスのためにオブジェクト構築をやめ、使用順に引数を並べたらこうなったということなのでしょう。
さて、これを静的型付け言語であるC++で表現できるか試してみました。結論から言うとC++11のvariadic templateを使えばできます。以下のコードはclang 3.0 (Apple ver.)で確認しました。
// 可変長引数replacerのスタブ例 #include <iostream> #include <string> using namespace std; template <typename ...Args> const string vreplace_impl(string src, std::string pattern, const string replacer(const string, int pos, const string)) { replacer("vreplace with 0 capture", 0, src); return ""; } template <typename ...Args> const string vreplace_impl(string src, std::string pattern, const string replacer(const string, const string p1, int pos, const string)) { replacer("vreplace with 1 capture", "p1", 0, src); return ""; } template <typename ...Args> const string vreplace(string src, std::string pattern, const string replacer(const string, Args...)) { vreplace_impl(src, pattern, replacer); return ""; } const string r0(const string part, int pos, const string src) { cout << "r0 " << part << "," << pos << "," << src << std::endl; return ""; } const string r1(const string part, const std::string p1, int pos, const string src) { cout << "r1 " << part << "," << pos << "," << src << std::endl; return ""; } int main() { vreplace("foobar", "foo", r0); // capureが含まれないのでr0 vreplace("foobar", "f(oo)", r1); // captureが一つ含まれるのでr1 return 0; } // 結果: // r0 vreplace with 0 capture,0,foobar // r1 vreplace with 1 capture,0,foobar
ただ、引数の数の決定の仕方が両言語では異なります。JS版ではパターンに含まれるキャプチャに依存してreplacerの引数を決定するのに対し、C++版ではreplaceに与えたreplacerのシグネチャに依存して、パターンのなかにキャプチャがいくつあるべきかを決定するのです。パターンの中のキャプチャの数がシグネチャと一致しない場合、おそらくランタイムエラーを投げることになるでしょう。
まあとにかく、実装はできそうです。もちろん、C++でこの種の処理を実装したいときはMatchedクラスを定義し、可変長部分はコンテナにするのがベストプラクティスだと思いますが。
参考文献:
15.5.4.11 String.prototype.replace (searchValue, replaceValue)
...
If replaceValue is a function, then for each matched substring, call the function with the following m + 3
arguments. Argument 1 is the substring that matched. If searchValue is a regular expression, the next m
arguments are all of the captures in the MatchResult (see 15.10.2.1). Argument m + 2 is the offset within string
where the match occurred, and argument m + 3 is string. The result is a String value derived from the original
input by replacing each matched substring with the corresponding return value of the function call, converted
to a String if need be.