13

§N3485の14.6.4.2は、従属候補関数のルックアップについて次のように述べています。

テンプレート定義とテンプレートで見つかった宣言だけでなく、関連付けられた名前空間内のルックアップで、すべての変換ユニットのそれらの名前空間に導入された外部リンケージを持つすべての関数宣言が考慮された場合、呼び出しの形式が正しくないか、より適切に一致することがわかります。インスタンス化コンテキストの場合、プログラムの動作は未定義です。

呼び出しが「不正な形式」であるとはどういう意味ですか。また、ルックアップによって不正な形式の呼び出しがどのように選択されるのでしょうか。また、すべての翻訳単位を考慮した場合に、より適切な一致が見つかることが重要なのはなぜですか?

4

4 に答える 4

14

呼び出しが「不正な形式」であるとは、正確にはどういう意味ですか

正式には、不正な形式は [defns.ill.formed] によって整形式ではないと定義され、整形式のプログラムは [defns.well.formed] によって次のように定義されます。

構文規則、診断可能なセマンティック規則、および One Definition Rule (3.2) に従って構築された C++ プログラム。

したがって、不適切な形式の呼び出しとは、構文が無効であるか、間違った数の引数を渡すなどの診断可能なエラー、パラメーターの型に変換できない引数、またはオーバーロードのあいまいさです。

不適切な形式の呼び出しがルックアップによってどのように選択されるでしょうか?

「(呼び出しの形式が正しくない場合 || より適切な一致が見つかる場合) 関連する名前空間内のルックアップで、外部リンケージを持つすべての関数宣言が考慮された場合 ...」と言っていると思います。他の関数は、同等またはより良い一致を見つけたでしょう。同様に良い一致は呼び出しをあいまいにします。つまり、不適切な形式になり、より良い一致は別の関数が呼び出されることになります。

したがって別のコンテキストでは呼び出しがあいまいだったり、別の種類のエラーを引き起こしたりしても、インスタンス化と定義のコンテキストで限られた名前のセットしか考慮していないために成功する場合、それは未定義です。また別のコンテキストで呼び出しがより適切な一致を選択した場合、それも未定義です。

また、すべての翻訳単位が考慮された場合に、より適切な一致が見つかることが重要なのはなぜですか?

このルールの理由は、同じテンプレートの特殊化を 2 つの異なるコンテキストでインスタンス化すると、2 つの異なる関数が呼び出されるような状況を許可しないためだと思います。たとえば、ある翻訳単位で呼び出しが 1 つの関数を見つけ、別の翻訳単位で別の関数を見つけた場合などです。関数を使用すると、ODR に違反する同じテンプレートの 2 つの異なるインスタンス化が取得され、リンカによって保持されるのは 1 つのインスタンス化だけです。テンプレートがインスタンス化された場所さえ見えません。

これは、前の段落の最後の文に似ています (まだカバーされていない場合)。

テンプレートの特殊化には、複数の翻訳単位でインスタンス化のポイントがある場合があります。インスタンス化の 2 つの異なるポイントが、1 つの定義規則 (3.2) に従ってテンプレートの特殊化に異なる意味を与える場合、プログラムは形式が正しくなく、診断は必要ありません。

C++ ARM (Ellis & Stroustrup) の 426 ページは、そのテキストのコンテキストを少し提供し (14.6.4.2 についても同様だと思います)、上記よりも簡潔かつ明確に説明しています。

これは、テンプレート内から使用されるグローバル名が、異なるコンパイル単位またはコンパイル単位内の異なるポイントで、異なるオブジェクトまたは関数にバインドされる可能性があることを暗示しているように思われます。ただし、それが発生した場合、結果のテンプレート関数またはクラスは、「1 つの定義」ルール (§7.1.2) によって無効になります。

[basic.def.odr]/6 には、同じルールの別の関連する定式化があります。

于 2013-03-06T22:03:05.373 に答える
8

問題は、名前空間を断片的に定義できるため、名前空間のすべてのメンバーを定義することが保証されている1 つの場所がないことです。その結果、異なる翻訳単位は異なる名前空間メンバーのセットを参照できます。このセクションが言っていることは、見えない部分がルックアップに影響する場合、動作は未定義であるということです。例えば:

namespace mine {
    void f(double);
}

mine::f(2); // seems okay...

namespace mine {
    void f(char);
}

mine::f(2); // ambiguous, therefore ill-formed

このルールは、 の最初の呼び出しf(2)が未定義の動作を生成することを示していmineます。これは、その時点で のすべてのオーバーロードが表示されていた場合、形式が正しくないためです。

于 2013-03-06T23:17:48.087 に答える
5

@tletnesの部分的な回答に基づいて、この特定の未定義の動作をトリガーする簡単なプログラムを思いついたと思います。もちろん、複数の翻訳単位を使用します。

cat >alpha.cc <<EOF
#include <stdio.h>
void customization_point(int,int) { puts("(int,int)"); }
#include "beta.h"
extern void gamma();
int main() {
    beta(42);
    gamma();
}
EOF

cat >gamma.cc <<EOF
#include <stdio.h>
void customization_point(int,double) { puts("(int,double)"); }
#include "beta.h"
void gamma() { beta(42); }
EOF

cat >beta.h <<EOF
template<typename T>
void beta(T t) {
    customization_point(t, 3.14);
}
EOF

このプログラムをさまざまな最適化レベルでコンパイルすると、その動作が変わります。「alpha.cc」の呼び出しは未定義の動作を呼び出すため、標準によれば、これは問題ありません。

$ clang++ alpha.cc gamma.cc -O1 -w ; ./a.out
(int,int)
(int,int)
$ clang++ alpha.cc gamma.cc -O2 -w ; ./a.out
(int,int)
(int,double)
于 2013-03-06T22:29:27.267 に答える
1

このルールを読んだとき、次のようなコードが少なくとも検討されていたものの一部であると思います。

int foo(int a; int b){ printf("A"); }

int main(){
   foo(1, 1.0);
}

int foo(int a, double b){ printf("B"); }

また

int foo(int a);

int main(){
   foo(1);
}

int foo(int a, double b){ printf("B"); }
于 2013-03-06T22:13:54.670 に答える