2020年6月25日木曜日

STLのtype_traitsを実装する その2

STLのtype_traitsを実装する その1の続き.

conjunctionを上手く実装できないか試行錯誤していて,たぶんこれでOKというのが出来たので書いておきます.

conditionalおよびconditional_tの実装

conditionalは,型のif文だと思えば良いでしょう.boolと型Tと型Fを渡すと,boolがtrueのときはtypeとしてTを,falseのときはtypeとしてFが手に入るわけです.

template <bool, class T, class F>
struct conditional
{
    using type = F;
};

template <class T, class F>
struct conditional<true, T, F>
{
    using type = T;
};

template <bool B, class T, class F>
using conditional_t = typename conditional<B, T, F>;

boolの値がtrueの場合を特殊化していますが,デフォルトはtypeがTになって,falseの場合はFになる,と逆にしても問題はありません.

conjunctionおよびconjunction_vの実装


conjunctionは,is_XXX系の型が特定の性質を持っているかを判定するtype traitsに対する論理積になります.通常の論理積と同じく,短絡評価が可能です.

つまり,is_integral_v<float> && is_floating_point_v<float>と書くと,最初のfloatが整数かどうかの判定でfalseになるので,全体としてfalseが決定するので,is_floating_point_v<float>は参照しない,というようなものです.

ただし,ここで問題なのが,&&でtype traitsの結果の論理積を取ってしまうと,参照はしないけれど,テンプレートの実体化は行われている,ということです.

例えば,has_valueというtype traitsがあって,与えられた型TがT::valueというような名前を持つかチェックする場合,has_value<int>が実体化されるとコンパイルエラーになるわけです.しかし,conjunctionを使うと,そういった問題は発生しません.

conjunction<is_floating_point<int>, int>;

最初の時点でfalseになるので,それ以降はテンプレートの実体化が行われず,2つ目のintはint::valueをチェックする,という処理が行われないわけです.

あとは,少しややこしいのが,親となるクラスを決定する方法です.

  • conjunction<>と実引数が0の場合,true_typeを継承.
  • conjunction<T0, T1, ..., Tn>の場合,Ti::valueがfalseなら,Tiを継承
  • conjunction<T0, T1, ..., Tn>の場合,全てのTi::valueがtrueなら,Tn(つまり最後の型)を継承

となるわけです.

というわけで,おそらく次のようになります.


// 実引数が0の場合は,ここで終わる
template <class... B>
struct conjunction : true_type {};

// 実引数が1の場合は,ここで終わる
template <class H>
struct conjunction<H> : conditional_t<static_cast<bool>(H::value), H, H>
{};

// それ以外
template <class H, class... T>
struct conjunction<H, T...> : conditional_t<static_cast<bool>(H::value), conjunction<T...>, H>
{};

template <class... B>
inline constexpr bool conjunction_v = conjunction<B...>::value;

最初の定義は,実引数が0の場合に適用されます.1つの場合,2つ目の定義で::valueを持っているかチェックした上でその型を利用します.
trueでもfalseでも,最後の1つならその型を使うことに変わりはないので,これで問題ないはずです.

3つ目の定義は,2つ以上ある場合で,trueなら残りをチェックし,falseならその型を継承するようにしています.

名前空間myに定義していたとして,次のようなテストをしています.

my::conjunction<my::is_integral<int>> c0;
my::is_integral<int> * pc0 = &c0;

my::conjunction<is_integral<int>, my::is_floating_point<int>> c1;
my::is_floating_point<int> * pc1 = &c1;

my::conjunction<is_integral<int>, my::is_floating_point<int>, int> c2;
my::is_floating_point<int> * pc2 = &c2;

// my::conjunction_v<int>; // コンパイルエラー(int::valueができないため)

static_assert(my::conjunction_v<>);
static_assert(my::conjunction_v<std::is_integral<int>>);
static_assert(my::conjunction_v<std::is_integral<int>, std::is_floating_point<float>>);
static_assert(!my::conjunction_v<std::is_integral<int>, std::is_floating_point<int>>);
// 短絡評価されるので,intを指定していても問題無し
static_assert(!my::conjunction_v<std::is_integral<float>, int>);

clangの実装でもMSVCの実装でも,condtionalを使っていなかったので,もしかしたら何か抜けがあるのかもしれませんが,今のところ上手く動作していそうです.

こういうパターンが網羅できないよ,というのがあれば指摘いただけると助かります.

0 件のコメント:

コメントを投稿