C++1y 軽量Concept

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>()});
}