特殊化が存在するかどうかをチェックする

機能の紹介

toml11では、ユーザー定義型との間にいくつかの変換方式を提供している。

1つめはtoml::valueを受け取るコンストラクタを使用するもの。続いてfrom_toml(const toml::value&)というメンバ関数を定義するもの。最後にtoml::from<T>という構造体を定義するもの。

このどれかが提供されているなら、ユーザー定義型でもtoml11は様々な箇所で自動変換を行ってくれる。

namespace user
{
struct vec
{
    explicit vec(const toml::value& v)
        : x(toml::find<double>(v)),
          y(toml::find<double>(v)),
          z(toml::find<double>(v))
    {}
    double x, y, z;
};
} // namespace user

const user::vec pos = toml::find<user::vec>(file, "position");

コンストラクタとメンバ関数を両方用意したのは正直若干失敗だったかなと思っている。メンバ関数版は内部的にデフォルトコンストラクタを要求するし、そうなると効率もあまりよくないからだ。まあ何かで使えることもあるかもしれないし、いきなりAPIを廃止するわけにはいかないので今の所そっとしておいている。

toml::from<T>は、メンバ関数やコンストラクタを追加できない外部ライブラリに対する対処法だ。外から変換関数を差し込むことができる。

これは、まず定義のないテンプレート構造体を用意しておいて、もし特殊化されていたならそれをつかうという方法で実装している。

namespace toml
{
template<typename T>
struct from; // 定義なし
} // toml

ユーザーがnamespace tomlを開かないといけないので若干微妙な気もする。この部分はもう少しなんとかできたんじゃないかなあと思っているが……。

実装

特殊化が存在するかどうかは以下のようにしてチェックしている。

struct has_specialized_from_impl
{
    template<typename T>
    static std::false_type check(...);
    template<typename T, std::size_t S = sizeof(toml::from<T>)>
    static std::true_type check(toml::from<T>*);
};

template<typename T>
struct has_specialized_from :
    decltype(has_specialized_from_impl::check<T>(nullptr)){};

定義があればsizeofの値が定まる。その場合下の関数が優先されてマッチする。そうでない場合、下の関数がマッチしないので上の関数で解決される。

これは結構、果たして大丈夫なのかなというコードだが、gcc, clang, MSVCのほぼありとあらゆるバージョンで動いているのでまあいいんじゃないかという気持ちで使っている(は?)。

問題と回避策

さて、今回もまたissue対応の話なのだが(解決した非自明な問題は記事にできるから便利だ)、これはEigen::VectorXdtoml::valueから変換しようとした際にtoml::getambiguousになるというものだ。

これは、Eigen::Matrixが(Eigen::VectorXdエイリアスである)以下のようなコンストラクタを持つことに起因する。

template<typename T>
explicit Matrix(const T& x);

これはありとあらゆるTに一致する。おそらく、数値そのものが来た時と、異なるサイズのMatrixなどが来たときのためにあるのだろうが、この関数は当然toml::valueにもマッチしてしまう。よって変換方法がambiguousになる。

これを回避するには、コンストラクタが存在してもtoml::from<T>が存在したならtoml::from<T>を優先するという順位を付けるしかない。そしてつけた。toml::getの条件をis_constructible<T, toml::value> && negation<has_specialized_from<T>>にしただけだが。

というわけで、変換方法に優先順位が入った。toml::from<T> == T.from_toml > constructorという順番になる。toml::from<T>T.from_tomlに優先順位をつけなかったのは、これはどちらもユーザーが足さない限り発生しないものなので、重複していたらエラーになってほしいのではないかと思ったからだ。

そんな感じです。あ、READMEにこのこと書くの忘れてた。書きます。