Concepts Lite: Constraining Templates with Predicates—Andrew Sutton, Bjarne Stroustrup
C++11では入らなかったConceptですが、現在SG 8の研究グループで議論が行われ、Clangで実験的な実装が行われていたりと、Conceptはまだ生きています。
今回のこれは、Conceptが持ついろいろな機能のうち、「テンプレートの型制約」の部分を、C++11で入った定数式の機能であるconstexprと組み合わせて考え直した案です。
まず、2つの値のうち、小さな値を返すmin()関数を考えてみます。
template <class T> T min(T a, T b) { return a < b ? a : b; }
min()関数に渡される任意の型Tは、<演算子で比較可能である必要があります。
C++11に提案されていたConceptでは、クラス定義のような構文で、型Tの要件を定義していました。
auto concept LessThanComparable<class T> { bool operator<(const T& a, const T& b); ... } // class TをLessThanComparable Tにすることで、型制約する template <LessThanComparable T> T min(T a, T b) { return a < b ? a : b; } // requiresを使った場合 template <class T> requires LessThanComparable<T> T min(T a, T b) { return a < b ? a : b; }
今回の案は、Conceptをconstexprな述語関数として定義します。型が満たす条件は、Type Traitsを使用します。
// <演算子を持っている、という制約の定義 template <class T> constexpr bool LessThanComparable() { return has_less<T>::value; // ※本来は、関連する比較演算子もチェックする } // constexpr述語関数を、テンプレート型制約として使用する template <LessThanComparable T> T min(T a, T b) { return a < b ? a : b; } // requiresを使った場合 template <class T> requires LessThanComparable<T>() T min(T a, T b) { return a < b ? a : b; }
型制約の定義には、Conceptの新構文を一切導入せず、使い慣れた単なる関数として定義できます。型制約を適用する側は、C++11に提案されていた構文をそのまま使用できます。単なる関数テンプレートなので、Concept Mapのようなことは特殊化でできますね。
requiresの方に指定するのは、単なる関数適用結果のbool定数式であるため、constexpr述語関数を定義せず、Type Traitsをそのまま書くこともできます。
template <class T> requires has_less<T>::value T min(T a, T b) { return a < b ? a : b; }
例は省きますが、requiresには&&や||を使用して、複数条件も書けます。また、テンプレート制約によるオーバーロードも可能です。
template <InputIterator Iterator> void advance(Iterator i, DifferenceType<T> a); template <BidirectionalIterator Iterator> void advance(Iterator i, DifferenceType<T> a); template <RandomAccessIterator Iterator> void advance(Iterator i, DifferenceType<T> a);
この案の難点としては、Concept定義の構文がないため、ユーザーが新たな型制約を定義する場合に、これまでSFINAEを駆使してやっていた「型Xはf()メンバ関数を持っているか」のようなことを、ユーザーに書かせるのは難しい、ということです。そのため、この案では、__is_valid_expr(expr)というような、コンパイラサポートの特殊関数を提供し、「型Tで、指定された式が評価可能か」というのを書きやすくすることが考えられています。
template <class T> constexpr bool EqualityComparable() { return __is_valid_expr(bool = {declval<T>() == declval<T>()}) && __is_valid_expr(bool = {declval<T>() != declval<T>()}); }