2.9クラステンプレート引数の演繹
2.9 テンプレートの実際のパラメーターの導出
C ++ 17までは、すべてのテンプレートパラメータタイプをクラステンプレートに渡す必要がありました(デフォルト値がない場合)。C ++ 17以降、常にテンプレート引数を明示的に指定する必要がある制約が緩和されました。代わりに、コンストラクタがすべてのテンプレートパラメータ(デフォルト値を持たない)を推定できる場合は、スキップしてテンプレート引数を明示的に定義できます。
C ++ 17より前は、すべてのテンプレート引数をクラステンプレートに渡す必要がありました(デフォルト値がない場合)。C ++ 17以降、テンプレート引数を明示的に指定する制限が緩和されました。逆に、コンストラクタがすべてのテンプレートパラメータを導出できる場合(これらのパラメータにはデフォルト値が含まれていない)、テンプレートの実際のパラメータの定義は無視できます。
たとえば、前のすべてのコード例では、テンプレート引数を指定せずにコピーコンストラクターを使用できます。
たとえば、前のすべてのコード例では、コピー構築を使用する場合、テンプレート引数を指定できません。
Stack < int > intStack1; // 文字列の スタックStack < int > intStack2 = intStack1; // OK:すべてのバージョンに適している Stack intStack3 = intStack1; // OK:C ++ 17から開始
いくつかの初期引数を渡すコンストラクターを提供することにより、スタックの要素タイプの推定をサポートできます。たとえば、単一の要素で初期化できるスタックを提供できます。
いくつかの初期パラメーターを渡すためのコンストラクターを提供することにより、スタック要素タイプの派生をサポートできます。たとえば、単一の要素によって初期化されたスタックを提供できます。
template <typename T> クラスStack { private : std :: vector <T> elems; // element public : Stack() = default ; Stack(T const&elem)// 要素を介してスタックを初期化する要素あり) :elems({elem}){ } … };
これにより、次のようにスタックを宣言できます。
これにより、次のようにスタックを宣言できます。
Stack intStack = 0 ; // C ++ 17以降、Stack <int>として派生
整数0でスタックを初期化することにより、テンプレートパラメータTがintであると推定され、Stack <int>がインスタンス化されます。
整数0を使用してスタックを初期化することにより、テンプレートパラメーターTをintとして導出できます。これにより、スタック<int>がインスタンス化されます。
次の点に注意してください。
次の点に注意してください。
•intコンストラクターの定義により、他のコンストラクターが定義されていない場合にのみデフォルトコンストラクターを使用できるため、デフォルトの動作でデフォルトコンストラクターを使用できるように要求する必要があります。
intコンストラクターが定義されているため、デフォルトのコンストラクターのデフォルトの動作が使用可能であることを要求する必要があります。他のコンストラクタを定義する場合、デフォルトのコンストラクタは提供されないためです。
Stack()= default ;
•引数elemは、中かっこで囲まれたelemsに渡され、elemを唯一の引数として持つ初期化子リストでベクターelemsを初期化します。
実際のパラメーターelemは中括弧を介してelemsに渡され、elemsのベクトルはelem要素が1つだけの初期化リストで初期化されます。
:elems({elem}) // 現時点では、elemsには要素(elem)が1つだけあります。
単一のパラメーターを初期要素として直接受け取ることができるベクトルのコンストラクターはありません。
Vectorは、最初の要素のコンストラクターとして単一のパラメーターを直接受け入れません。
関数テンプレートの場合とは異なり、クラステンプレート引数は(テンプレート引数の一部のみを明示的に指定することによって)部分的にしか推定できないことに注意してください。詳細については、315ページのセクション15.12を参照してください。
関数テンプレートとは異なり、クラステンプレートパラメーターは部分的にしか導出できないことに注意してください(一部のテンプレート引数のみが明示的に指定されています)。詳細については、315ページのセクション15.12を参照してください。
文字列リテラルを使用したクラステンプレート引数の演繹
文字列リテラルを使用してクラステンプレート引数を派生させる
原則として、文字列リテラルでスタックを初期化することもできます。
原則として、文字列リテラルを使用してスタックを初期化することもできます。
Stack stringStack = " bottom " ; // C ++ 17から、Stack <char const [7]>として派生
しかし、これは多くの問題を引き起こします。一般に、参照によってテンプレート型Tの引数を渡す場合、パラメーターは減衰しません。これは、生の配列型を対応する生のポインター型に変換するメカニズムの用語です。つまり、Stack <char const [7]>を実際に初期化し、Tが使用されている場合は常にchar const [7]型を使用します。たとえば、タイプが異なるため、サイズの異なる文字列をプッシュすることはできません。詳細については、115ページの7.4節を参照してください。
しかし、これは多くの問題を引き起こします:通常、テンプレートタイプTの実際のパラメーターが参照によって渡される場合、実際のパラメーターのタイプは縮退しません(減衰)。ここでの「減衰」という用語は、「元の配列タイプ」を指します対応するオリジナルのポインタ型に「メカニズム」。(注釈:文字列リテラルは配列型です。配列が参照によって渡されると、「配列参照」型に推定され、ポインタに退化しません)
ただし、テンプレートタイプTの引数を値で渡す場合、パラメーターは減衰します。これは、生の配列型を対応する生のポインター型に変換するメカニズムの用語です。つまり、コンストラクターの呼び出しパラメーターTはchar const *であると推定され、クラス全体がStack <char const *>であると推定されます。
ただし、テンプレートタイプTの実際のパラメーターが値で渡される場合、実際のパラメータータイプは縮退します(「減衰」という用語は上記のとおりです)。つまり、派生コンストラクターの呼び出しパラメーターTはchar const *であるため、クラス全体の型はStack <char const *>になります。
このため、引数が値で渡されるようにコンストラクターを宣言することは価値があるかもしれません:
このため、引数を値で渡すコンストラクタを宣言することは価値があります。
テンプレート<typename T> クラスStack { private : std :: vector <T> elems; // 要素 public : Stack(T elem)// 要素を渡してスタックを値で初期化する :elems({elem}){ / / クラステンプレートを導出するため(減衰) } … };
これにより、次の初期化は正常に機能します。
このようにして、次の初期化は正常に機能します。
Stack stringStack = " bottom " ; // C ++ 17以降、Stack <char const *>として派生
ただし、この場合は、一時的なelemをスタックに移動して、不必要にコピーしないようにする必要があります。
ただし、この場合は、elem一時オブジェクトをスタックに移動して、不要なコピーを回避することをお勧めします。
template <typename T> クラスStack { private:std :: vector <T> elems; // 要素 public : Stack(T elem)// 値によって1つの要素でスタックを初期化します :elems({std :: move(elem)}){ } … };
控除ガイド
派生ウィザード
コンストラクターを値で呼び出すように宣言する代わりに、別の解決策があります。コンテナーで生のポインターを処理することは問題の原因となるため、コンテナークラスの生の文字ポインターの自動推定を無効にする必要があります。
値でパラメーターを渡すようにコンストラクターを宣言することに加えて、別の解決策があります。コンテナーで生のポインターを処理することが問題の原因であるため、生の文字ポインターを自動的に派生させるコンテナーの機能を無効にする必要があります。
特定の控除ガイドを定義して、既存のクラステンプレート引数の控除を追加または修正できます。たとえば、文字列リテラルまたはC文字列が渡されるたびに、スタックがstd :: stringに対してインスタンス化されるように定義できます。
特別な「派生ウィザード」を定義して、既存のクラステンプレートパラメータの派生をさらに提供または修正できます。たとえば、文字列リテラルまたはC文字列が渡されるたびに、std ::文字列スタックとしてインスタンス化されるように定義できます。
Stack(char const *)-> Stack <std :: string >;
このガイドは、クラス定義と同じスコープ(名前空間)に表示される必要があります。
ウィザードは、クラス定義と同じスコープ(名前空間)に表示される必要があります。
通常はクラス定義に従います。->に続くタイプを控除ガイドのガイド付きタイプと呼びます。
通常はクラス定義に従います。「->」に続くタイプをガイドのタイプを「派生ガイド」と呼びます。
さて、宣言
さて、ステートメントは
スタックstringStack { " bottom " }; // OK:Stack <std :: string>はC ++ 17以降に推定された
スタックをStack <std :: string>と推定します。
スタックはタイプStack <std :: string>に推定されます。
ただし、以下はまだ機能しません。
ただし、以下はまだ無効です。
Stack stringStack = " bottom " ; // Stack <std :: string>として派生しましたが、まだ無効です。
Stack <std :: string>をインスタンス化するためにstd :: stringを推定します。
スタック<std :: string>をインスタンス化するためにstd :: stringを派生させます
class Stack { private : std :: vector <std :: string > elems; // 要素 public : Stack(std :: string const&elem)// 1つの要素でスタックを初期化します :elems({elem}){ } … };
ただし、言語規則により、std :: stringを期待するコンストラクターに文字列リテラルを渡すことによって、オブジェクトを初期化(=を使用して初期化)することはできません。したがって、次のようにスタックを初期化する必要があります。
ただし、言語規則に従って、コピーの初期化( "="初期化を使用)によってstd ::文字列を必要とするコンストラクターに文字列リテラルを渡すことはできません。したがって、スタックを次のように初期化する必要があります(注釈:直接初期化とコピー初期化は異なります。記事の後半の「プログラミング実験」を参照してください)。
Stack stringStack { " bottom " }; // Stack <std :: string>として派生し、有効です。
疑わしい場合は、クラステンプレートの引数の控除がコピーされることに注意してください。stringStackをStack <std :: string>として宣言した後、次の初期化では、文字列スタックである要素によってスタックを初期化する代わりに、同じ型を宣言します(したがって、コピーコンストラクターを呼び出します)。
疑問がある場合は、クラステンプレート引数の派生のコピーを使用してください。stringStackをStack <std :: string>タイプとして宣言すると、次の初期化宣言はすべて同じタイプになります。したがって、スタック要素の代わりにコピーコンストラクターを呼び出してスタックを初期化します。
Stack stack2 {stringStack}; // Stack <std :: string>として派生 Stack Stack3(stringStack); // Stack <std :: string>として派生 Stack stack4 = {stringStack}; // <std :: stringとして派生>
クラステンプレート引数の控除の詳細については、313ページのセクション15.12を参照してください。
クラステンプレート引数の派生の詳細については、313ページのセクション15.12を参照してください。
【プログラミング実験】導出ウィザード
#include <iostream> #include <vector> using namespace std; class Test { public : Test(const std :: string ){} }; template <typename T> class Stack { private:std :: vector <T> elems; // 要素 public : Stack() = default ; Stack(const T&elem)// ここで参照渡しできることに注意してください! :elems({elem}){ } // スタック(const char * elem){} }; Stack(const char *)-> Stack <std :: string >; // Derivation Wizard int main() { / * *****直接初期化とコピー初期化の違い***** * / // 1。直接初期化:最初に「abcd」を文字列に変換し、次にTest(string)// 2を 渡し ます。コピーの初期化の暗黙の変換要件:「abcd」からTestへの直接の生成は直接生成する必要があります。途中に他の一時オブジェクトがあってはなりません文字列など)。 Test t1(" abcd "); // OK // Test t2 = "abcd"; // エラー、初期化要件を "abcd"からTestにコピーすると、直接変換できます。つまり、テストを生成する前に文字列に変換することはできません。 // 直接変換を行うには、Test(const char *)を呼び出す必要がありますが、このクラスはそのようなコンストラクタを提供しないため、 // コンパイルは失敗します。 / ******派生ウィザード***** * / // Stack st1 = "abcd"; // 派生のタイプはStack <std :: string>ですが、コンパイルできません。 // ここでは、言語の特性により、コピーして初期化するとき( "="を使用して初期化する) // 文字列リテラル(const char *タイプ)をStack(std :: string)のコンストラクターに渡すことはできません。 // Stack(const char *)コンストラクターをStack <String>に追加すると、それをコンパイルできます。原則 // t1オブジェクトの構成を確認します。 Stack st2(" abcd "); // OK、Stack(const char *)-> Stack(std :: string) Stack st3 { " abcd " }; // OK、同上。 // コピーを通じて、コピーコンストラクターを呼び出してStack <std ::を初期化します Stack st4 = st2; // OK、Stackのコピーコンストラクターを呼び出すStack st5(st2); // Stack st6と同じ {st2}; // return 0と同じ ; }