C ++でのテンプレートの使用から、個人的には、パラメーターの制限を設定するための標準的なメカニズムが不足していることを常に恐れていました。 言い換えれば、開発者が関数を書くとき
template <class T> bool someFunc(T t) { if (t.someCheck()) { t.someAction(0); } }
彼は、タイプ
Tのオブジェクトの機能に関してさまざまな仮定を立てていますが、ユーザーにそれらを伝える標準的な能力はありません。 したがって、上記の例では、少なくとも次のことを前提としています。
- タイプTのオブジェクトは値によって渡されます。つまり、オープンコピーコンストラクターが必要です。
- パラメーターなしのパブリックメソッドT :: someCheckがあります。これは、論理型に変換される値を返します
- openメソッドT :: someActionがあります。これは、数値型に変換できる1つのパラメーターを取ることができます
問題
ここで、プログラマーが
someFuncをライブラリーとして配布することにしたとしましょう。 ユーザーは既存の制限についてどのように知ることができますか?
- ライブラリのドキュメントを読む。 それが明確に書かれている場合。 ただし、この場合でも、コードを変更する前に使用されるすべてのライブラリのドキュメントを読む人はいません。 すべての条件を暗記することも、誰もが処理できるわけではありません。
- ライブラリのソースコードを調べます。 また、アマチュアのためのレッスン。 さらに、ライブラリが大きくなり、プロジェクトが複雑になるほど、恋人が少なくなります
コンパイルエラーに基づいて構築するためのもう1つのオプション(実際には、唯一の自動オプション)が残っています。 つまり 理由を探さずに、変更を加えました...しかし、C ++テンプレートを使用した人は、エラーメッセージがどのように見えるかを知っています。 「ここで修正すれば動作します。」などのプロンプトは表示されません。 メッセージが十分に明確である場合もあれば、他の誰かのライブラリの荒野にいる場合もあります...コンパイラは、発生した場所でエラーを報告します-元の使用コンテキストが復元できないことは彼には関係ありません。
例について考えてみましょう(後で説明します)
リスト(標準コンテナ)をソートする必要があります。 何も前兆はない、我々は書く
std::list<int>theList; std::sort(theList.begin(), theList.end());
コンパイルされません。 VS2013では、エラーは次のとおりです
エラーC2784: 'unknown-type std :: operator-(std :: move_iterator <_RanIt>&、const std :: move_iterator <_RanIt2>&)': 'std :: move_iterator <_RanIt>&'のテンプレート引数を推定できませんでしたfrom 'std :: _ List_iterator <std :: _ List_val <std :: _ List_simple_types <int> >>' c:\プログラムファイル(x86)\ microsoft visual studio 12.0 \ vc \ include \ algorithm 3157 1 MyApp
しかし、これはそれほど悪いことではありません-誤ってクリックすると、ここで標準
アルゴリズムライブラリの奥深くにいることに気付きます。
template<class _RanIt, class _Pr> inline void sort(_RanIt _First, _RanIt _Last, _Pr _Pred) {
最初の反応:「何?! なぜベクターがソートされたが、リストが突然ソートされなかった-両方のコンテナーにイテレーターがあり、どちらも要素の順序を知っている..」 しかし、あなたが他の人の腸に投げ込まれたと想像してください。生命ブイのないそれほど有名な図書館ではありません...
解決策
解決策があることがわかりました。 この方向で言語を変更するイニシアチブ
が存在しますが 、これまでのところ標準に含まれていません。
ただし、
boostライブラリは、概念の概念をサポートしており、テンプレートパラメーターのカスタム制約を作成できます。
概念を使用するためのアルゴリズムは次のとおりです。 開発者は、ライブラリと一緒に、正しい操作に必要な概念の説明を提供します。 ユーザーは、提案されたルールに準拠しているかどうか、すべてのエンティティを自動的にテストできます。 この場合、エラーは次の形式ですでにより明確になります
。クラスは、「デフォルトコンストラクターが必要」という概念をサポートしていません 。
ブーストを使用すると、開発者は毎回コンセプトをゼロから構築する必要がなくなります-ライブラリには事前に作成された基本的な
制限が含ま
れています。
記事の冒頭にある
someFunc関数の例を検討してください。 最初のルール-コピーコンストラクターの可用性は、既製の
Boost :: CopyConstructibleコンセプトでカバーされています。残りは、テストを手動で記述する必要があります。
#include <boost/concept_check.hpp> template <class T> struct SomeFuncAppropriate { public: BOOST_CONCEPT_ASSERT((boost::CopyConstructible<T>)); BOOST_CONCEPT_USAGE(SomeFuncAppropriate) { bool b = t.someCheck();// someCheck, , bool t.someAction(0);// someAction , } private: T t; // must be data members };
したがって、ブーストの概念はテンプレート構造であり、そのパラメーターはテスト済みの型です。 マクロBOOST_CONCEPT_ASSERTを使用して、既成概念への準拠を確認します。 注意してください-パラメータとして、括弧内の概念が彼に渡されます。その結果、二重の括弧が必要ですが、目を痛めます。
カスタムチェックは、マクロBOOST_CONCEPT_USAGEを使用して実装できます。 テストに関係するすべてのインスタンス(
T tがあります )
は 、ローカル変数としてではなく
、クラスのメンバーとして宣言する必要があることを覚えておくことが重要です。
概念が宣言されると、同じマクロBOOST_CONCEPT_ASSERTを使用して、準拠性を確認できます。 クラスがあるとしましょう
class SomeClass { public: SomeClass(); void someCheck(); int someAction(int); private: SomeClass(const SomeClass& other); };
このようにテストできます
BOOST_CONCEPT_ASSERT((SomeFuncAppropriate<SomeClass>));
実行しようとしました-すぐにエラーが発生します
エラーC2440:「初期化」:「void」から「bool」に変換できません
そしてクリックすると、問題の原因を簡単に理解できる
SomeFuncAppropriateコンセプトの定義(BOOST_CONCEPT_USAGE)の破線にスローされます
-someCheckメソッドは
boolの代わりに
voidを返し
ます 。 修正し、もう一度お試しください...
エラーC2248: 'SomeClass :: SomeClass':クラス 'SomeClass' boost \ concept_check.hppで宣言されたプライベートメンバーにアクセスできません
エラーをクリックすることで、コンセプトのソースコードに自分自身を見つけます
BOOST_concept(CopyConstructible,(TT)) { BOOST_CONCEPT_USAGE(CopyConstructible) { TT a(b);
そして、カーソルは行を指します
TT a(b);
そうそう-コピーコンストラクターは非表示です。 これを修正しました-テストに合格しました(ファイルはBOOST_CONCEPT_ASSERTでコンパイルされます)。 これは、
SomeClassクラスが
someFunc関数の開発者の期待を完全に満たしていることを
意味します。 互換性に違反する変更が将来追加された場合でも、コンセプトチェックにより、問題が何であるかがすぐにわかります。
std :: sortを使用して、
std :: listソートの例に戻りましょう。 ソート可能なコンテナの要件を概念として表しています。 まず、
std :: sortは、ランダムアクセスをサポートするコンテナでのみ機能します。 対応する概念はboost(
boost :: RandomAccessContainer )ですが、それだけでは十分ではありません。 コンテナの内容にも要件があります-その要素は、比較演算子「less」をサポートする必要があります。 ここで、boostは、
boost :: LessThanComparableの既製の概念に役立ちます。
概念を1つにまとめる
template <class T> struct Sortable { public: typedef typename std::iterator_traits<typename T::iterator>::value_type content_type; BOOST_CONCEPT_ASSERT((boost::RandomAccessContainer<T>)); BOOST_CONCEPT_ASSERT((boost::LessThanComparable<content_type>)); };
テストを実行する
BOOST_CONCEPT_ASSERT((Sortable<std::list<int> >));
見る
エラーC2676:バイナリ '[': 'const std :: list <int、std :: allocator <_Ty >>'は、この演算子または定義済み演算子boost \ concept_check.hppで受け入れられる型への変換を定義しません
エラーをクリックすると、
RandomAccessContainerコンセプトのソースコードが表示され、違反していることが明確になります。
std :: listを
std :: vectorに置き換えた場合、コンセプトのチェックは成功します。 次に、
SomeClassインスタンスのソートベクトルを確認してみましょう。
BOOST_CONCEPT_ASSERT((Sortable<std::vector<SomeClass> >));
コンテナは現在適切ですが、
SomeClassは小なり演算子を定義していないため、ソートすることはまだ不可能です。 私たちはすぐにそれについて知るでしょう
エラーC2676:バイナリ '<': 'SomeClass'はこの演算子を定義していないか、事前定義演算子boost \ boost \ concept_check.hppで受け入れられる型への変換を定義していません
エラーをクリックすると、ソースコード
LessThanComparableに自分自身が
見つかり 、違反内容を正確に
把握できます。
したがって、概念により、C ++での一般化されたプログラミングが少し極端になります。 喜ぶ以外に何もできない!