C++ コア ガイドライン:概念の使用規則

C++20 では高い確率でコンセプトが得られます。これらを使用するための C++ コア ガイドラインのルールは次のとおりです。

まず、一歩下がってみましょう。コンセプトとは?

  • コンセプト コンパイル時の述語です。これは、概念がコンパイル時に評価され、ブール値を返すことができることを意味します。

次の質問は。 C++ の概念の長所は何ですか?

コンセプト

  • プログラマーがインターフェースの一部として要件を直接表現できるようにする
  • テンプレート パラメータの要件に基づいて、関数のオーバーロードとクラス テンプレートの特殊化をサポートします。
  • テンプレート パラメータの要件と適用されたテンプレート引数を比較することで、大幅に改善されたエラー メッセージを生成します。
  • ジェネリック プログラミングのプレースホルダーとして使用できます。
  • コンセプトを定義する力を与える

さあ、一歩前進です。今日の 4 つのルールは次のとおりです。

  • T.10:すべてのテンプレート引数の概念を指定する
  • T.11:可能な限り標準的な概念を使用する
  • T.12:auto よりも概念名を優先する ローカル変数用
  • T.13:単純な単一型の引数の概念には簡略表記を使用する

最初のルールから始めましょう。

T.10:すべてのテンプレート引数のコンセプトを指定する

このルールに追加することはあまりありません。正確さと読みやすさのために、すべてのテンプレート パラメータに概念を使用する必要があります。詳細な方法で実行できます。

template<typename T>
requires Integral<T>()
T gcd(T a, T b){
 if( b == 0 ){ return a; }
 else{
 return gcd(b, a % b);
 }
}

または、もっと簡潔にすることもできます。

template<Integral T>
T gcd(T a, T b){
 if( b == 0 ){ return a; }
 else{
 return gcd(b, a % b);
 }
}

最初の例では、required 節で概念を指定していますが、概念 Integral を使用できます。 キーワード typename または class の代わりに。コンセプト Integral ブール値を返す定数式でなければなりません。

std::is_integral を使用してコンセプトを作成しました 型特性ライブラリから。

template<typename T>
concept bool Integral(){
 return std::is_integral<T>::value;
}

私が行ったように概念を定義することは、最善の考えではありません。

T.11:可能な限り標準コンセプトを使用する

可能であれば、Guidelines Support Library (GSL) または Ranges TS の概念を使用する必要があります。私たちが持っているものを見てみましょう。 GSL の概念は主に Ranges TS の一部であるため、無視します。以下は、ドキュメント N4569:Working Draft, C++ Extension for Ranges からの Range TS の概念です。

コア言語の概念

  • Same
  • DerivedFrom
  • ConvertibleTo
  • Common
  • Integral
  • Signed Integral
  • Unsigned Integral
  • Assignable
  • Swappable

比較の概念

  • Boolean
  • EqualityComparable
  • StrictTotallyOrdered

オブジェクトの概念

  • Destructible
  • Constructible
  • DefaultConstructible
  • MoveConstructible
  • Copy Constructible
  • Movable
  • Copyable
  • Semiregular
  • Regular

呼び出し可能なコンセプト

  • Callable
  • RegularCallable
  • Predicate
  • Relation
  • StrictWeakOrder

これらの概念のそれぞれが何を意味するのかを知りたい場合は、既に述べたドキュメント N4569 が答えを提供します。概念の定義は、型特性ライブラリに基づいています。たとえば、概念 Integral, Signed Integral の定義は次のとおりです。 、および Unsigned Integral .

template <class T>
concept bool Integral() {
 return is_integral<T>::value;
}

template <class T>
concept bool SignedIntegral() {
 return Integral<T>() && is_signed<T>::value;
}

template <class T>
concept bool UnsignedIntegral() {
 return Integral<T>() && !SignedIntegral<T>();
}

関数 std::is_integral<T>std::is_signed<T> 型特性ライブラリの述語です。

さらに、標準ライブラリの期待を定義するために C++ 標準のテキストで使用される名前があります。それらは強制されていない概念ですが、 std::sort などのアルゴリズムの要件を文書化しています .

template< class RandomIt >
void sort( RandomIt first, RandomIt last );

std::sort の最初のオーバーロード 2 つの RandomAccessIterato が必要です rの。ここで、RandomAccessIterator とは何かを言わなければなりません。 です:

  • A RandomAccessIterator BidirectionalIterator です 一定時間内に任意の要素を指すように移動できます。
  • A BidirectionalIterator ForwardIterator です 両方向に動かすことができます
  • A ForwardIterator イテレータです 指された要素からデータを読み取ることができます。
  • イテレータ 要件は、コンテナの要素を識別してトラバースするために使用できる型を記述します。

C++ 標準のテキストで使用されている名前付き要件の詳細については、cppreference.com を参照してください。

T.12:auto よりも概念名を優先する ローカル変数用

auto 制約のない概念 (プレースホルダー) ですが、制約のある概念を使用する必要があります。制約のないプレースホルダー (自動) を使用できる各状況で、制約のある概念を使用できます。これが直感的なルールではない場合は?

これが私の主張をするための例です。

// constrainedUnconstrainedConcepts.cpp

#include <iostream>
#include <type_traits>
#include <vector>

template<typename T> // (1)
concept bool Integral(){ 
 return std::is_integral<T>::value;
}

int getIntegral(int val){
 return val * 5;
}

int main(){
 
 std::cout << std::boolalpha << std::endl;
 
 std::vector<int> myVec{1, 2, 3, 4, 5};
 for (Integral& i: myVec) std::cout << i << " "; // (2)
 std::cout << std::endl; 

 Integral b= true; // (3)
 std::cout << b << std::endl;
 
 Integral integ= getIntegral(10); // (4)
 std::cout << integ << std::endl;
 
 auto integ1= getIntegral(10); // (5)
 std::cout << integ1 << std::endl;
 
 std::cout << std::endl;

}

概念 Integral  を定義しました 行(1)で。したがって、行 (2) の範囲ベースの for ループと変数 b で積分を反復処理します。 および integ inline (3) と (4) は整数でなければなりません。 (5) については、私はそれほど厳密ではありません。ここでは、制約のない概念で問題ありません。

最後に、プログラムの出力。

T.13:単純な単一型の引数の概念には簡略表記を使用する

C++ コア ガイドラインの例はまったく無害に見えますが、テンプレートの記述方法に革命を起こす可能性があります。ここにあります。

template<typename T> // Correct but verbose: "The parameter is
// requires Sortable<T> // of type T which is the name of a type
void sort(T&); // that is Sortable"

template<Sortable T> // Better (assuming support for concepts): "The parameter is of type T
void sort(T&); // which is Sortable"

void sort(Sortable&); // Best (assuming support for concepts): "The parameter is Sortable"

この例は、関数テンプレート sort を宣言する 3 つのバリエーションを示しています。 .すべてのバリエーションは意味的に同等であり、テンプレート パラメーターが概念 Sortable をサポートする必要があります。 .最後のバリエーションは関数宣言のように見えますが、パラメーターは概念であり具象型ではないため、関数テンプレート宣言です。もう一度言います: sort コンセプト パラメータにより、関数テンプレートになります。

次は?

C++ コア ガイドラインでは、「優れた概念を定義することは簡単ではありません。概念は、アプリケーション ドメインの基本的な概念を表すことを目的としています」と述べています。次の投稿でそれが何を意味するか見てみましょう。