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を使っていなかったので,もしかしたら何か抜けがあるのかもしれませんが,今のところ上手く動作していそうです.
こういうパターンが網羅できないよ,というのがあれば指摘いただけると助かります.