C ++ 20での概念的な並べ替え

事前に変更に備えておく方が良いので、C ++ 20標準、つまりconceptに含まれるものを検討することをお勧めします。







コンセプトステータス



現在、概念には技術仕様( TS:技術仕様 )のステータスがあります: ISO / IEC TS 19217:2015を説明する文書。 このようなドキュメントは、言語標準の革新を採用する前に、これらの革新をC ++コミュニティでテストおよび調整するために必要です。 gccコンパイラは、2015年以降、実験モードで概念の技術仕様をサポートしています。







技術仕様のコンセプトと現在のドラフトC ++ 20のコンセプトは異なるが、それほど多くないことは注目に値します。 この記事では、技術仕様のバリアントについて説明します。







理論



クラスおよび関数テンプレートは制限を受ける場合があります。 制約は、テンプレート引数に要件を課します。 概念は、このような制約の名前付きセットです。 各概念は、これらの制約をテストするブール関数( 述語 )です。 検証は、概念または制限に関連付けられたテンプレートをインストールする際のコンパイル段階で実行されます。 そのようなチェックが失敗すると、コンパイラはどのテンプレート引数がどの制約のチェックに失敗したかを示します。







練習する



概念の意味と目的が理解できたので、構文を検討できます。 概念の定義には、変数と関数の2つの形式があります。 変数の形式に興味があります。 通常のテンプレート変数の定義に非常に似ていますが、 conceptキーワードがあります。







template<typename T> concept bool MyConcept = /* ... */;
      
      





コメントの代わりに、boolにキャストされるconstexpr式を記述する必要があります。 この式は、テンプレート引数の制限です。 テンプレートを概念に限定するには、typename(またはclass)の代わりにその名前を使用する必要があります。







たとえば、整数の場合:







 // (         //     ) #include <type_traits> template<typename T> //    concept bool MyIntegral = std::is_integral<T>::value; //template <typename T> template<MyIntegral T> bool compare (T a, T b) { return a < b; } void foo () { compare (123u, 321u); /// OK compare (1.0, 2.0); /** :    MyIntegral (std::is_integral<double>::value = false) */ }
      
      





要件式を使用して、より複雑な制約を設定できます。 要件式は、 整形式、式の戻り値、型の存在を検証できます。 構文はここで十分に解析されます







 #include <unordered_set> #include <vector> template<typename T> concept bool MyComparable = requires (T a, T b) { a < b; /// ,     { a < b } -> bool; /// ,      bool }; template<MyComparable T> bool compare (T a, T b) { return a < b; } void foo () { std::vector<int> vecA = {1, 2, 3}, vecB = {1, 2, 4}; std::unordered_set<int> setA = {1, 2, 3}, setB = {1, 2, 4}; compare (vecA, vecB); /// OK compare (setA, setB); /**    MyComparable std::unordered_set    .  ( a < b )  . */ }
      
      





仕分け



並べ替えを書く際に概念はどのように役立ちますか アルゴリズム自体は変更されませんが、概念を使用してソートパターンを改善できます。 この例を考えてみましょう:







 #include <algorithm> struct NonComparable {}; int main () { std::vector<NonComparable> vector = {{}, {}, {}, {}, {}, {}, {}, {}}; std::sort (vector.begin(), vector.end()); //  }
      
      





エラーは、NonComparable構造に比較演算がないことです。 コンパイラエラーがどのように見えるか想像できますか? そうでない場合は、スポイラーの下を見てください。







gcc(7.2.1)CentOS
 [username@localhost concepts]$ g++ -std=c++17 main.cpp In file included from /opt/rh/devtoolset-7/root/usr/include/c++/7/bits/stl_algobase.h:71:0, from /opt/rh/devtoolset-7/root/usr/include/c++/7/vector:60, from main.cpp:1: /opt/rh/devtoolset-7/root/usr/include/c++/7/bits/predefined_ops.h: In instantiation of 'constexpr bool __gnu_cxx::__ops::_Iter_less_iter::operator()(_Iterator1, _Iterator2) const [with _Iterator1 = __gnu_cxx::__normal_iterator<NonComparable*, std::vector<NonComparable> >; _Iterator2 = __gnu_cxx::__normal_iterator<NonComparable*, std::vector<NonComparable> >]': /opt/rh/devtoolset-7/root/usr/include/c++/7/bits/stl_algo.h:81:17: required from 'void std::__move_median_to_first(_Iterator, _Iterator, _Iterator, _Iterator, _Compare) [with _Iterator = __gnu_cxx::__normal_iterator<NonComparable*, std::vector<NonComparable> >; _Compare = __gnu_cxx::__ops::_Iter_less_iter]' /opt/rh/devtoolset-7/root/usr/include/c++/7/bits/stl_algo.h:1921:34: required from '_RandomAccessIterator std::__unguarded_partition_pivot(_RandomAccessIterator, _RandomAccessIterator, _Compare) [with _RandomAccessIterator = __gnu_cxx::__normal_iterator<NonComparable*, std::vector<NonComparable> >; _Compare = __gnu_cxx::__ops::_Iter_less_iter]' /opt/rh/devtoolset-7/root/usr/include/c++/7/bits/stl_algo.h:1953:38: required from 'void std::__introsort_loop(_RandomAccessIterator, _RandomAccessIterator, _Size, _Compare) [with _RandomAccessIterator = __gnu_cxx::__normal_iterator<NonComparable*, std::vector<NonComparable> >; _Size = long int; _Compare = __gnu_cxx::__ops::_Iter_less_iter]' /opt/rh/devtoolset-7/root/usr/include/c++/7/bits/stl_algo.h:1968:25: required from 'void std::__sort(_RandomAccessIterator, _RandomAccessIterator, _Compare) [with _RandomAccessIterator = __gnu_cxx::__normal_iterator<NonComparable*, std::vector<NonComparable> >; _Compare = __gnu_cxx::__ops::_Iter_less_iter]' /opt/rh/devtoolset-7/root/usr/include/c++/7/bits/stl_algo.h:4836:18: required from 'void std::sort(_RAIter, _RAIter) [with _RAIter = __gnu_cxx::__normal_iterator<NonComparable*, std::vector<NonComparable> >]' main.cpp:6:44: required from here /opt/rh/devtoolset-7/root/usr/include/c++/7/bits/predefined_ops.h:43:23: error: no match for 'operator<' (operand types are 'NonComparable' and 'NonComparable') { return *__it1 < *__it2; } ~~~~~~~^~~~~~~~ In file included from /opt/rh/devtoolset-7/root/usr/include/c++/7/bits/stl_algobase.h:67:0, from /opt/rh/devtoolset-7/root/usr/include/c++/7/vector:60, from main.cpp:1: /opt/rh/devtoolset-7/root/usr/include/c++/7/bits/stl_iterator.h:888:5: note: candidate: template<class _IteratorL, class _IteratorR, class _Container> bool __gnu_cxx::operator<(const __gnu_cxx::__normal_iterator<_IteratorL, _Container>&, const __gnu_cxx::__normal_iterator<_IteratorR, _Container>&) operator<(const __normal_iterator<_IteratorL, _Container>& __lhs, ^~~~~~~~ /opt/rh/devtoolset-7/root/usr/include/c++/7/bits/stl_iterator.h:888:5: note: template argument deduction/substitution failed: In file included from /opt/rh/devtoolset-7/root/usr/include/c++/7/bits/stl_algobase.h:71:0, from /opt/rh/devtoolset-7/root/usr/include/c++/7/vector:60, from main.cpp:1: /opt/rh/devtoolset-7/root/usr/include/c++/7/bits/predefined_ops.h:43:23: note: 'NonComparable' is not derived from 'const __gnu_cxx::__normal_iterator<_IteratorL, _Container>' { return *__it1 < *__it2; } ~~~~~~~^~~~~~~~  ..
      
      





コード内のこのような小さなエラー、およびコンパイラー内の非常に大きなエラー。 アビドニー、はい!?







このようなエラーは概念の助けを借りて減らすことができます。そのため、それらを使用してラッパーを作成します。 ソートにはイテレータが必要なので、ソート可能なイテレータの概念を記述する必要があります。 このようなイテレーターには、いくつかの小さなコンセプトが必要です。 たとえば、比較可能なオブジェクト(上記)、交換されたオブジェクト:







 template<typename T> concept bool MySwappable = requires (T a, T b) { std::swap(a, b); //   };
      
      





ローミングオブジェクト







 template<typename T> concept bool MyMovable = requires (T a) { T (std::move(a)); //    a = std::move(a); //    };
      
      





ランダムアクセス反復子







 template<typename T> concept bool MyRAIterator = requires (T it) { typename T::value_type; //       it++; //     Random Access  it--; it += 2; it -= 2; it = it + 2; it = it - 2; { *it } -> typename T::value_type; //   };
      
      





すべての単純な概念の準備ができたら、Sortableイテレータの複合概念を定義できます。







 template<typename T> concept bool MySortableIterator = MyRAIterator<T> && //    MyMovable<typename T::value_type> && //   MyComparable<typename T::value_type> && //   MySwappable<typename T::value_type>; //  
      
      





それを使用して、ラッパーが記述されます。







 template<MySortableIterator T> void conceptualSort (T begin, T end) { std::sort (begin, end); }
      
      





比類のないオブジェクトで「概念」ソートを呼び出すと、







 struct NonComparable {}; int main () { std::vector<NonComparable> vector = {{}, {}, {}, {}, {}, {}, {}, {}}; conceptualSort (vector.begin(), vector.end()); //  }
      
      





コンパイルエラーは16行しかかかりません。







gcc(7.2.1)CentOS
 [markgrin@localhost concepts]$ g++ -std=c++17 -fconcepts main.cpp main.cpp: In function 'int main()': main.cpp:49:49: error: cannot call function 'void conceptualSort(T, T) [with T = __gnu_cxx::__normal_iterator<NonComparable*, std::vector<NonComparable> >]' conceptualSort (vector.begin(), vector.end()); ^ main.cpp:41:6: note: constraints not satisfied void conceptualSort (T begin, T end) { ^~~~~~~~~~~~~~ main.cpp:36:14: note: within 'template<class T> concept const bool MySortableIterator<T> [with T = __gnu_cxx::__normal_iterator<NonComparable*, std::vector<NonComparable> >]' concept bool MySortableIterator = MyRAIterator<T> && MyMovable<typename T::value_type> && ^~~~~~~~~~~~~~~~~~ main.cpp:12:14: note: within 'template<class T> concept const bool MyComparable<T> [with T = NonComparable]' concept bool MyComparable = requires (T a, T b) { ^~~~~~~~~~~~ main.cpp:12:14: note: with 'NonComparable a' main.cpp:12:14: note: with 'NonComparable b' main.cpp:12:14: note: the required expression '(a < b)' would be ill-formed
      
      





もちろん、最初はエラーの内容を理解するのはまだそれほど簡単ではありませんが、いくつかの「概念的な」エラーの後、数秒で読み始められます。







おわりに



もちろん、 エラーの長さを減らすことがイノベーションの唯一の利点ではありません。 テンプレートは制限により安全です。 名前付きの概念のおかげで、コードはより読みやすくなります(最も頻繁に使用されるものはライブラリに含まれます )。 一般的に、C ++はその機能(テンプレート)部分で拡張されます。








All Articles