13

テンプレートに関する Josuttis と Vandevoorde の著名な著書C++ Templates: The Complete Guideでは、関数テンプレートのオーバーロードに関する詳細が説明されています。

関数シグネチャとオーバーロードされた関数テンプレートの説明に関連する例の 1 つで、次の用語で説明するコードを示しています。

This program is valid and produces the following output:

(Note: Output shown below)

ただし、Visual Studio 2010 で同じコードをビルドしてコンパイルすると、異なる結果が得られます。これにより、VS 2010 コンパイラが正しくないコードを生成しているか、コードが有効であるという Josuttis が間違っていると思われます。

これがコードです。(Josuttis 2003、セクション 12.2.1)

// File1.cpp

#include <iostream>

template<typename T1, typename T2>
void f1(T2, T1)
{
    std::cout << "f1(T2, T1)" << std::endl;
}

extern void g();

int main()
{
    f1<char, char>('a', 'b');
    g();
}

...

// File2.cpp

#include <iostream>

template<typename T1, typename T2>
void f1(T1, T2)
{
    std::cout << "f1(T1, T2)" << std::endl;
}

void g()
{
    f1<char, char>('a', 'b');
}

f1()(2 つのテンプレート関数定義の型引数の反転に注意してください。このコード例の 2 つの関数のように、2 つの型引数が同じ場合、この反転は効果がないことにも注意してください。)

Josuttisによると:

This program is valid and produces the following output:

f1(T2, T1)
f1(T1, T2)

Visual Studio 2010 コンパイラで同じコードを変更せずにビルドして実行すると、次の結果が得られます。

f1(T1, T2)
f1(T1, T2)

f1さらに、コンパイラ/リンカがfile1.cppでインスタンス化された関数とfile2.cppでインスタンス化された関数をどのように区別できるのか疑問に思っていました.コンパイラはf1すべての「知識」を取り除いていると思います.これらの関数はテンプレートから作成され、関数シグネチャ自体の情報 (私が思うに) しか持っていないという事実: void (char, char)、これは両方のf1関数で同じです。

(私が正しければ) 関数シグネチャが 2 つの翻訳単位で同一であるため、これはOne Definition Rule (ODR) 違反の例であり、無効な C++ であると考えられます。

ただし、先ほど述べたように、Josuttis と Vandevooorde は、これが有効なC++ であると主張しています。

しかし、同じコードをコンパイルしたバージョンでは、 Josuttis が主張する出力とは異なる結果が得られるため、これは、VS 2010 が間違ったコードを生成しているか、この場合 Josuttis が間違っていることを示しているようです (つまり、コードが無効であり、 ODR)。

Josuttis と Vandevooorde は間違っていますか、それとも VS 2010 が間違った出力を生成していますか? または、VS 2010 が生成する出力と Josuttis が報告する出力との不一致を説明する他の説明はありますか?

f1()それぞれが呼び出された時点での VS 2010 の逆アセンブリを示すことは興味深いかもしれません。

の最初の呼び出しf1()( 内で直接main()):

main() 内から直接呼び出される f1()

の 2 回目の呼び出しf1()( 内からg()):

g() 内から呼び出される f1()

どちらの場合も、コンパイラによって選択されるアドレスはf1()同じ 13E11EAh であることに注意してください。私には、これは実際、コンパイラーが 2 つのインスタンス化された関数シグネチャーを区別できないことを示しています。これは、ODR に違反しているケースであるため、コードは無効な C++であり、Josuttis の著書には誤りがあります。しかし、それはただの兆候です。知らない。

(本のウェブサイトで正誤表を確認しましたが、この例についての言及はありません。)

補遺コメントからのリクエストにより、このプログラムの .map ファイルから関連する出力を添付していますf1

<code>f1</code> の破損した名前を示す .map ファイル出力

補遺2質問に答えたので - Josuttis の本は正しいです - Josuttis のテキストの同じセクション (12.2.1) で、テンプレートを含む一意の関数シグネチャを決定するものが正確に概説されていることに注意してください。側面

テキスト (関数シグネチャを定義するその他の期待されるもの) から、TRANSLATION UNIT は関数シグネチャの一部です。テンプレート関数 (のみ) の場合、RETURN TYPE は関数シグネチャの一部であり、

.6. 関数が関数テンプレートから生成された場合は、テンプレート パラメーターとテンプレート引数。

したがって、それは明らかです。関数テンプレートがインスタンス化された後でも、コンパイラ/リンカーがテンプレートに必要な特別な規則に従うために、テンプレート情報はコンパイラによって維持および追跡される必要があります (私の質問のコード例の場合のように)。

4

1 に答える 1

8

以前の間違った回答をお詫びします。この例は確かに正しいようで、実際には標準自体にも同様の例があります (C++11、14.5.6.1/1-2)。その全文を引用させてください:

  1. 関数テンプレートをオーバーロードして、2 つの異なる関数テンプレートの特殊化が同じ型を持つようにすることができます。【例:

    // file1.c
    
    template<class T> void f(T*);
    
    void g(int* p) {
        f(p); // calls f<int>(int*)
    }
    
    
    // file2.c
    
    template<class T> void f(T);
    
    void h(int* p) {
        f(p); // calls f<int*>(int*)
    }
    

    終了例]

  2. このような特殊化は別個の機能であり、1 つの定義規則 (3.2) に違反しません。

あなたの場合、2 つの異なる関数テンプレートがあり、どちらも呼び出されf1(関数テンプレートをオーバーロードできるので問題ありません)、同じ型の特殊化があります。

于 2012-11-29T16:32:26.837 に答える