メインコンテンツへスキップ

SFINAEの制約式を少しだけ読みやすくするConstraints

·756 文字·
技術解説 C++ SFINAE
komori-n
著者
komori-n

最近知ったSFINAEを少しだけ読みやすくするおまじないの紹介。

C++17 で SFINAE を使って関数を定義したりしなかったりしたいとき、std::enable_ifstd::nullptr を組み合わせて以下のようなコードを書くことがしばしばある1

template <typename T,
          std::enable_if_t</* SFINAE特有の式 */, std::nullptr_t> = nullptr>
void func(T&& t) {
    /* ... */
}

SFINAE に馴染みのない人にとってはかなり読みづらい。やりたいことは型 T に応じて関数の定義を切り替えることだが、関係ない std::nullptr_tnullptr が並ぶことで少し読みづらく感じることがある。また、SFINAE の条件が増えれば増えるほど std::enabler_if_t の template 引数が長くなり、可読性が低下しやすい。

このようなとき、次のような Constraints を導入することでほんの少しだけ読みやすくなる。

namespace detail {
template <typename... Args>
struct ConstraintsImpl {
  using Type = std::nullptr_t;
};
}  // namespace detail

template <typename... Args>
using Constraints = typename detail::ConstraintsImpl<Args...>::Type;


// Constraints を用いた定義方法
template <typename T,
          Constraints<std::enable_if_t</* SFINAE特有の条件1 */>,
                      std::enable_if_t</* SFINAE特有の条件2 */>,
                      /* SFINAE特有の条件式... */> = nullptr>
void func(T&& t) {
    /* ... */
}

Constraints の中身は単純で、テンプレート引数を無視して常に std::nullptr_t を返している。こうすることで、std::nullptr_t を隠蔽し、少しだけ読みやすくできる。また、std::enable_if を複数個並べられるため、条件が AND であることがわかりやすくなる。

// 使用例
template <typename T,
          Constraints<std::enable_if_t<std::is_default_constructible_v<T>>,
                      std::enable_if_t<std::is_nothrow_assignable_v<T>>> = nullptr>
void func(T&& t) noexcept {
    std::cout << "T is default constructible and nothrow assignable" << std::endl;
}

ぱっと見ではそれほど見た目が変わらないように見えるかもしれない。しかし、実際に使ってみると想像以上に SFINAE コードが読みやすくなるため、今後は enabler や ALWAYS_TRUE ではなく Constraints パターンを採用していきたい。

Related

一度きりしか呼べないファンクターを管理したい
·758 文字
技術解説 C++ SFINAE
SFINAEでtemplate classのメンバ関数の実体化を制御する
·1843 文字
技術解説 C++ SFINAE STL
C++で型推論結果を手っ取り早く知りたいとき
·842 文字
技術解説 C++