toml11をバージョンアップした

しすぎじゃない?

変更点

まずは、C++17になっていたら文字列をstd::string_viewで受け取ったり、toml::valuestd::string_viewで初期化できるようにした。どうやらC++17で使っている人がいたっぽいので、特に要求はなかったがあったほうがいいかなと思って入れた。C++17の新機能の中でもstring_viewはかなり待ち望まれていた機能でもあるし、使っている人も比較的多いのではないか。

あとは、値を取り出すときに、簡単に書くためにas_integer()とかas_string()みたいなのを入れた。これは複雑な変換はできないが、今ちょっとだけ中身を覗きたい、というときに仰々しくならないので便利だ。

const auto v = toml::parse("sample.toml");
if(v.is_integer() && v.as_integer() == 42) // あっさり
{
  // ...
}

あまり順当でない変更としては、コメントを取得できるようにしたことだろう。以下のようなファイルがあったとき、

# comment for a.
a = 42

b = 3.14 # comment for b.
const auto data = toml::parse("sample.toml");
std::cout << data.at("a").comment();
// "# comment for a."
std::cout << data.at("b").comment();
// "# comment for b."

のようにして関連するコメントを取り出せる。

こんな機能足したら重くなるんじゃない? というのは当然の疑問だと思うが、重くはならない。ここまでの実装からコスト無しに導入できる機能だったので、それと要望もあったので、入れた。

前に記事を書いたとおり、toml11では、各toml::valueがファイル内のどの領域に対応するかを保持している。これは前にも記事を書いたとおり、エラーメッセージのためだ。値が文字列であることを期待して変換しようとしたが失敗した、というとき、何行目で何が書かれているのか、型は何で何への変換に失敗したのかなどをパースが終わってからも出力するためだ。以下のように。

terminate called after throwing an instance of 'toml::type_error'
  what():  [error] toml::value bad_cast to integer
 --> example.toml
 3 | title = "TOML Example"
   |         ~~~~~~~~~~~~~~ the actual type is string

ここでは、整数だと思って取り出そうとしたら文字列だった、というエラーが表示されている。これを表示するにはファイル名と部分文字列と行数を覚えておく必要がある。だがそれを全てのtoml::valueが覚えるのはしんどいので(配列とかだと部分文字列が超長くなることもある)、実際にはファイルの全内容とファイル名を入れた構造体へのshared_ptrと、その中で何文字目から何文字目かだけをtoml::valueは覚えている。ファイル中での位置はパースの時も使っているから、廃物利用だ。地球に優しい(無駄なコピーを発生させないことによってエネルギー消費を抑えているため)。

で、ここでファイル内の全データとその値が対応する場所を覚えているということは、その周囲にあるコメントを検索できるということでもある。value.comment()を呼ぶと周囲のコメントを探し、見つけた分だけ返してくれる。よってtoml::valueにはデータメンバは一切追加されていない。また、パース時もコメントは今までどおりガン無視している。もしコメントが必要なら後で探せばいいので。パース時はガン無視しているのに後でコメントを取得することもできるというのはちょっとおもしろい。

今後の方向性

問題点としては、これは単にコメントを取得できるようにしただけであって、コメントを編集することはできないということだ。編集できないならあまり意味もないかと思い、シリアライズの時も気にしていない。ついでに、値のデータが変更された時は、基本的にファイル内のどの領域に対応していたかという情報は失われる。なので、まあまだあまり役には立たないだろう。

将来的には、シリアライズの際にコメントを色々できるようにしてみたい気持ちは少しある。だが、コメントは必要無い場合が多かろうとも思うので、必要のない場合はオフにできる機能であってほしい。だがtoml::valueの構造を変えるとなるとかなり大きな変更ということになるので、メジャーアップデートとして出したい。

そうなってくると他にもやりたいことが色々ある。例えば、初期はキャメルケースを使っていたが途中でスネークケースに切り替えたなどの経緯で滅茶苦茶になっている命名を統一したい(今の所、キャメルケースのAPIは一切触らずに使えるようになってはいるので、最近知って使ってみようとしてくれている人は「そうだったの?」くらいの話かも知れない)。命名を直すのはとてもやりたくてウズウズしているが確実にユーザーコードをぶっ壊すのでやってこなかった。メジャーアップデートにしかならないが、名前を直すだけのメジャーアップデートって……と二の足を踏み続けている。だが便利機能がいっぱい入るならついでにやるのはありかもしれない。移行準備は色々やらないとダメだろうが……(片方をdeprecateしておいて両方使える期間を取るとか)。

あと、toml::parsetoml::tableを返す(toml::valueではなく)のも修正したい。これは初期からのことで、TOMLファイルがテーブルに一対一対応するからなのだが、今やvalueはただのtagged-unionではなく、ファイル内の位置などの情報を持ってしまっているし、その情報を利用するために「toml::valueをテーブルだと見当を付けて値を検索する」というような機能を足してきているので、逆に不自然になってしまった。今になって見るとtoml::tableは、ただのunordered_map<key, value>エイリアスなので、多機能なtoml::valueに比べて前面に押し出すにはあまりにも簡素すぎる。当時は全てが簡素だったのでバランスも取れていたのだが、toml::valueの機能が増えて重点がそっちに移ってしまった。

他には、コンテナの種類を選べないのも気になってはいた。std::unordered_mapは定数時間でルックアップができるが、メモリをそれなりに使う。std::mapの方が良い場面もそれなりにある、というような記事は見るし、かなり使う要素でもあるので、そうなると選べるようにしておきたい。そうしておけば、flat_mapが入った時も対応が簡単だ。その場合、まさしく型を変えるのだから、これはテンプレートを使う以外の方法はない。あと、配列を格納するときにstd::vectorを使うことに文句を付ける人は少ないかも知れないが、一応std::dequeとかに切り替えられるようにするのはありかも知れない。

だが、普通に考えるとtoml::valueがテンプレート引数を取るとは思わないので、そこは隠さないといけない。デフォルトテンプレート引数はこういうときは不便で、C++17以前だとtoml::value<> v = ...のような奇妙な宣言になってしまう。なのでデフォルト引数でうまく隠すことはできない。隠すとしたら、std::stringのようにエイリアスを使うしかない。つまり、toml::basic_value<...>を作っておいて、using toml::value = toml::basic_value<...>のようにするのだ。

そうしておけば、コンテナの種類も変えられるし、コメントをstd::stringのようにして別に保持するかどうかも決められる。アロケータも変えられるようにしてもいいのかもしれない。toml::valueにはデフォルトの型を設定する必要があり、それを決めるのも難しくはあるが、まあ何とかなるだろう。型パラメータの異なるtoml::value間の変換は難しい話だ。それに関してはサポートしなくてもいいかもしれない。他の問題としては例えば、toml::arraytoml::tableのような、コンテナの型に依存してしまう型名はややこしいことになるが、デフォルトの型を入れておくということでよいのではないだろうか。

指定はどうするのが良いかだが、これは決まっている。関数テンプレートの場合はデフォルトパラメータを使うときに<>が必要無いことを利用して、toml::parseをテンプレートまみれにすればよい。autoで受け取っていれば簡単だ。

const auto v = toml::parse("sample.toml"); // デフォルト
const auto v = toml::parse<toml::comment::preserve, std::map, std::deque>("sample.toml"); // カスタム

のようにすればいいのだ。

こうすれば、デフォルトでいい場合は今まで通りの書きかたで動き、カスタマイズしたい場合は必要に応じて設定を色々できるということになり、悪くない辺りに落ち着くのではないだろうか。むしろ何で最初からこうしなかったんだろ。多分パーサを書くところで手一杯だったんだろうな。

とまあ、やりたいことは沢山あるのだが、やることが結構種類があって手が回らないというのと、疲れているのかわからないが「よっしゃ書くぞ!」となる時間が短くなってきている気がするのが懸念材料だ。上記のを全て盛り込むととても大きな変更になるが、バージョン3、いつ出せるだろう。