それでも構造体をtupleに変換したい

前 最近書いたマクロ(構造体をtupleに変換したい) - TXT.TXT


これもC++ Advent Calendar 2016の9日目の記事です

はじまり

struct S
{
    int x;
    std::string s;
};

構造体に

bool operator<(S const&lhs,S const&rhs)
{
    return std::tie(lhs.x,lhs.s) < std::tie(rhs.x,rhs.s);
}

自動生成できるものを自動生成したい
それにはやっぱりtupleへの変換が必要だ

構造化束縛なら?

C++17(1z)で構造化束縛(Structured Bindings)という機能が入る
C++1z 構造化束縛 - Faith and Brave - C++で遊ぼう
詳しくは上などを見てほしいが結論を言うと

template<class T>
auto to_tie(T&&s)
{
    auto &[x1,x2] =s;
    return std::tie(x1,x2);
}

こういうこういう関数が作れる
[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

ただ残念ながら自分は[X1,X2]の部分を可変長にする方法は知らない
これは必要なだけ書きなぐれば解決するのでそこまで困らないのだが
問題はこれをSFINAEの文脈で使う方法を思いつかないので適切な関数に振り分ける方法が思いつかない


なのでコンストラクタの引数の数から推定しようと思う
万能解とは言えないが、今回のケースではうまく行くし大丈夫な時は大丈夫だろう
だめでも構造化束縛で失敗するだろうし

実装

面倒なので1-3要素まで
まず構造体の引数の数をコンストラクタの引数の数から推測する

template<class T>auto impl(Nice<3>*)->decltype(T{{},{},{}},Idx<3>{}){return {};}
template<class T>auto impl(Nice<2>*)->decltype(T{{},{}},Idx<2>{}){return {};}
template<class T>auto impl(Nice<1>*)->decltype(T{{}},Idx<1>{}){return {};}
template<class T>
auto len(){return impl<T>(static_cast<Nice<3>*>(nullptr));}

それを元に構造体束縛でtieする

template<class T>
auto impl2(T&s,Idx<1>){auto &[x1] = s;return std::tie(x1);}
template<class T>
auto impl2(T&s,Idx<2>){auto &[x1,x2] = s;return std::tie(x1,x2);}
template<class T>
auto impl2(T&s,Idx<3>){auto &[x1,x2,x3] = s;return std::tie(x1,x2,x3);}

template<class T>
auto to_tie(T&s){return impl2(s,len<T>());}

後はそれを元にやりたいことをやる

template<class T>
bool cmp(T const&lhs,T const&rhs)
{
    return to_tie(lhs) < to_tie(rhs);
}

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ


できないこと

T x={ {},{}/*略*/,{}};

の形式で初期化できない構造体はこの方法は使えない
explicitとかあるとまずいかもしれない
(これに関しては特に困らなさそうなので深く考えていないが、もしかしたらなんかいい方法があるのかもしれない)

また、マクロを使った場合にくらべ型名を取ることができないのでjson objectで鍵を持たせるなどができない


そして最大の問題は手元のコンパイラで動かない。未来はようカモン