C++関数リロード、関数テンプレートと関数テンプレートリロード、どちらを選択しますか?

15032 ワード

じゅうかいせき
C++では、関数のリロード、関数テンプレート、および関数テンプレートのリロードについて、どの関数定義(特に複数のパラメータを呼び出す場合)を呼び出すかを選択する良いポリシーが必要です.このプロセスをリロード解析と呼びます.
△このプロセスは非常に複雑になります.このようなコードを書かなければならないときに遭遇しないことを願っています.
 
おおよその手順
  Ⅰ.候補関数のリストを作成します(候補関数と同じ名前の関数とテンプレート関数が含まれます).
  Ⅱ.候補関数リストを使用した実行可能関数リストの作成(パラメータの数が正しいことが要求されます.このため、実パラメータタイプが対応するパラメータタイプと完全に一致する場合を含む暗黙タイプ変換シーケンスがあります.たとえば、floatパラメータの関数呼び出しを使用して、doubleタイプに変換してdoubleタイプに一致させ、テンプレート関数はfloatタイプに関数インスタンスを生成できます).
  Ⅲ.最適な実行可能関数があるかどうかを決定します(呼び出しがある場合、ない場合はエラーを報告します).
 
パラメータが1つしかない関数を例に挙げます.
 1 may('B');    //     
 2 
 3 /*            */
 4 void may(int);                          // #1
 5 float may(float, float = 3);        // #2
 6 void may(char);                        // #3
 7 char * may(const char *);            // #4
 8 char may(const char &);              // #5
 9 template<class T> void may(const T &);    // #6
10 template<class T> void may(T *);          // #7

これらの関数宣言は、名前が同じであるため、関数リストに入ります.次に、戻り値を考慮せずにフィーチャータグ(パラメータの数とタイプ)を考慮します.整数が暗黙的にタイプをポインタタイプに変換できないため、#4と#7は実行できません.#6は、Tがcharタイプに置き換えられた場合、残りの5つの実行可能な関数を生成するために使用できます.(#1、#2、#3、#5、#6).この時点で1つしか残っていない場合は、いずれも正しく使用できます.
  
次は、選好コーナーに着きました.このステップでは,関数呼び出しパラメータが実行可能な候補関数のパラメータと一致するために必要な変換を主に考慮する.通常、最適から最悪までの順序は次のとおりです.
1、完全に一致し、関数はテンプレートより優れている.
2、リフト変換(例えば、charとshortはintに自動的に変換され、floatはdoubleに自動的に変換される).
3、標準変換(例えば、intはchar、longはdouble).
4.クラス宣言で定義された変換など、ユーザー定義の変換.
残りの5つの関数のうち、#1は#2より優れている.なぜなら、charからintはアップグレード変換であり、charからfloatは標準変換であるからである(この場合、#1,#3,#5,#6が残っている).#3,#5,#6は#1および#2より優れている.彼らは完全に一致しているからである(#3,#5,#6が残っている).#3および#5は#6より優れている.#6はテンプレートであるためである(#3および#5が残っている).
このとき、2つの問題が発生します.完全に一致するのはいったい何ですか.2つの完全一致(#3と#5)がある場合はどうすればいいですか?通常、2つの完全一致はエラーですが、このルールには2つの例外があります.
 
完全一致と最適一致
完全に一致する場合、C++は「重要でない変換」を許可し、次の表にこれらの変換をリストします.Typeは任意のタイプを表します.例えば、intからint&、Typeはchar&のようなタイプであってもよいので、これらのルールにはchar&からconst char&への変換も含まれる.
完全に一致許容される重要でない変換
実参から
パラメータへ
Type
Type &
Type & 
Type
Type[]
* Type
Type(パラメータリスト)
Type(*)(パラメータリスト)
Type
const Type
Type
volatile Type
Type *
const Type
Type *
volatile Type *
次のコードがあるとします.
1 struct blot {int a; char b[10]};
2 blot ink = {25, "spots"};
3 recycle(ink);
4 
5 //          
6 void recycle(blot);          // #1 blot to blot
7 void recycle(const blot);   // #2 blot to const blot
8 void recycle(blot &);     // #3 blot to blot &
9 void recycle(const blot &); // #4 blot to const blot &

複数の完全に一致するプロトタイプがある場合は、リロード解析プロセスを完了できません.最適な実行可能関数がない場合は、コンパイラがエラーを報告します.
ただし、このような例外ルールは、まず、非constデータへのポインタと参照が非constポインタと参照よりも優先され、前例では、#3と#4のみが定義されている場合、inkがconstとして宣言されていないため、#3が選択されるが、constと非constの違いはポインタと参照が指すデータにのみ適用され、すなわち、#1と#2のみが定義されている場合、二義的なエラーが発生します.
1つの完全整合が他の場合よりも優れているもう1つの場合、1つは非テンプレート関数であり、もう1つは非テンプレート関数であり、この場合、非テンプレート関数はテンプレート関数(明示的な具体化を含む)よりも優先される.
2つの完全に一致する関数がテンプレート関数である場合、より特定のテンプレート関数が優先されます.これは、テンプレートの暗黙的な生成よりも具体化が優れていることを示します.
1 struct blot {int a; char b[10]};
2 template <class Type> void recycle(Type t);  //   
3 template <> void recycle(blot & t);    //      
4 
5 blot ink = {25, "spots"};
6 recycle(ink);    //       

「最も具体的」という用語は、必ずしも具体化を示すことを意味するものではなく、コンパイラがどのタイプを使用するかを推定するときに実行される変換が最も少ないことを意味する.例:
1 struct blot {int a; char b[10]};
2 template <class Type> void recycle(Type t);    // #1
3 template <class Type> void recycle(Type * t);  // #2
4 
5 blot ink = {25, "spots"};
6 recycle(&ink);    //   #2,      ,#2        

最も具体的なテンプレートを見つけるためのルールを部分ソートルールと呼ぶ.
 
部分ソート規則
 1 template 
 2 void show(T arr[], int n);     // #1
 3 
 4 template 
 5 void show(T * arr[], int n);   // #2
 6 
 7 struct debts
 8 {
 9     char name[50];
10     double amount;
11 };
12 
13 ......
14 
15 int things[6] = {13,31,103,301,310,130};
16 debts mr[3] = 
17 {
18    {"aaa", 24.1},
19    {"bbb", 25.2},
20    {"ccc", 26.3}   
21 };
22 double * pd[3];
23 
24 for(int i=0; i<3; i++)
25 {
26    pd[i] = &mr[i].amount;          
27 }
28 
29 show(things, 6); //   #1
30 show(pd, 3);    //   #2

thingsはint配列であり、1と一致し、Tがintに置き換えられる.pdはdouble*配列であり、1と一致するとTがdouble*に置き換えられ、2と一致するとTがdoubleに置き換えられる.この2つのテンプレートでは、配列内容がポインタであるという特定の仮定を行ったため、#2がより具体的に使用される.プログラムから#2を削除すると、#1を使用して値ではなくアドレスが表示されます.
要するに、リロード解析は最も一致する関数を探し、このような関数が1つしか存在しない場合は、それを選択します.このような関数が複数存在するが、テンプレート以外の関数が1つしかない場合は、それを選択します.入伏よ適切な関数が複数存在し、テンプレート関数ですが、他の関数よりも具体的な関数が1つしかない場合は選択します.その他の場合(テンプレートまたはテンプレート以外の関数は複数ありますが、他より具体的な関数や一致しない関数は1つもありません)はエラーです.
 
カスタム選択の作成
場合によっては、コンパイラを起動して希望の選択を行うことができます.
 1 template<class T>
 2 T lesser(T a, T b);           // #1
 3 
 4 int lesser(int a, int b);     // #2
 5 
 6 ......
 7 
 8 int m = 20;
 9 int n = -30;
10 double x = 15.5;
11 double y = 25.9;
12 
13 lesser(m, n);                //   #2
14 lesser(x, y);                //   #1,T    double  
15 lesser<>(m, n);              // <>     ,      ,  #1
16 lesser<int>(x, y);           //      ,          x,y      int  

 
複数のパラメータの関数
複数のパラメータの関数呼び出しを複数のパラメータのプロトタイプと一致させると、状況は非常に複雑になります.コンパイラは、すべてのパラメータの一致を考慮する必要があります.他の実行可能な関数よりも適切な関数が見つかった場合は、その関数を選択します.1つの関数は他の関数よりも適切であり、すべてのパラメータの整合度は他の関数よりも悪くなく、少なくとも1つのパラメータの整合度が他の関数よりも高い必要があります.