12

次のコードを考えると:

#include <memory>
#include <iostream>

using namespace std;

template<typename T>
void test(T & value) {
  cout << "most generic" << endl;
}

template<typename T>
void test(shared_ptr<T> & value) {
  cout << "shared_ptr" << endl;
}

class A {};

int main(int argc, char ** argv) {
  A a;
  shared_ptr<A> p(new A());
  test(a);
  test(p);
  return 0;
}

コールはなぜ

test(p)

2 つのシグネチャを区別できないと不平を言う代わりに、T = A を使用して 2 番目の形式のテストをインスタンス化しますか?

4

1 に答える 1

22

どちらもオーバーロード解決の実行可能な選択肢ですが、2 番目の関数テンプレートは最初のテンプレートよりも特化されているためです。

C++11 標準のパラグラフ 13.3.3/1 によると:

[...] これらの定義を考えると、実行可能な関数 F1 は、すべての引数 i について、ICSi(F1) が ICSi(F2) よりも悪い変換シーケンスではない場合、別の実行可能な関数 F2 よりも優れた関数であると定義されます。

— 一部の引数 j に対して、ICSj(F1) は ICSj(F2) よりも優れた変換シーケンスです。そうでない場合は、

— コンテキストは、ユーザー定義の変換 (8.5、13.3.1.5、および 13.3.1.6 を参照) による初期化と、F1 の戻り値の型から宛先の型 (つまり、初期化されるエンティティの型) への標準の変換シーケンスです。 F2 の戻り値の型から宛先の型への標準の変換シーケンスよりも優れた変換シーケンスです。[...] または、そうでなければ、

— F1 は非テンプレート関数で、F2 は関数テンプレートの特殊化です。そうでない場合は、

— F1 と F2 は関数テンプレートの特殊化であり、F1 の関数テンプレートは、14.5.6.2 で説明されている半順序規則に従って、F2 のテンプレートよりも特殊化されています。

§ 14.5.6.2 では、関数テンプレートが別の関数テンプレートよりも特殊化されていると判断される方法について説明しています。特に、14.5.6.2/2 あたり:

部分的な順序付けでは、各テンプレートを順番に変換し (次の段落を参照)、関数の型を使用してテンプレートの引数推定を実行することにより、2 つの関数テンプレートのどちらが他方よりも特殊化されているかを選択します。推論プロセスは、テンプレートの 1 つが他のテンプレートよりも特化されているかどうかを判断します。その場合、より特殊化されたテンプレートが部分順序付けプロセスによって選択されます。

標準の正式な定義を解読するのは非常に難しい場合がありますが、その複雑さは通常、ほとんどの状況で自然に期待されるように言語を明確に動作させることを目的としています。

あなたが提供するオーバーロードについて私たちが持つことができる直感的な期待はstd::shared_ptr<T>、引数が type の場合に a を受け入れるものを選択する必要があるというstd::shared_ptr<int>ことstd::shared_ptr<>です。制約のない過負荷よりも。

この直感的な期待を明確な一連のルールに変換するための正式な手順は複雑に聞こえるかもしれませんが、この過負荷かどうかを判断したい私たちの状況では、それに従うことは特に難しいことではありません。

template<typename T>
void f(std::shared_ptr<T>);

これよりも専門的です:

template<typename U>
void f(U);

この場合、直感に基づいてどちらがより特化されているかを判断するのは簡単ですが、コンパイラはアルゴリズムに依存する必要があり、このアルゴリズムはすべての状況で機能する必要があります。

この場合、メカニズムは次のようになります。

  1. 最初のオーバーロードを取得し、そのテンプレート パラメーターTを型引数 (任意の型引数) に置き換えます。たとえばint、対応するシグネチャをインスタンス化します。関数パラメーターは型を持つことになりますstd::shared_ptr<int>
  2. 入力としてその型 (この場合) のオブジェクトを提供することで2 番目のオーバーロードを呼び出し、そこから型を推測することは常に可能ですか?shared_ptr<int>U
  3. そうですね、答えはイエスです。Uであると推測されますstd::shared_ptr<int>
  4. 逆に: 2 番目のオーバーロードを取得し、そのテンプレート パラメーターUを任意の型引数 (たとえば) に置き換えbool、対応するシグネチャをインスタンス化します。関数パラメーターの型はbool;になります。
  5. その型 ( ) のオブジェクトを引数として指定して最初のオーバーロードを呼び出し、そこから型を推測することは常に可能ですか?boolT
  6. ここでの答えは、もちろんいいえです。が一致すると推測する方法はありませTん。std::shared_ptr<T>bool
  7. 結論:最初のオーバーロードは、2 番目のオーバーロードよりも特化しています。

もちろん、複数のテンプレート パラメータと複数の関数パラメータがある場合は、少し複雑になりますが、メカニズムはほとんど同じです。

于 2013-03-19T10:32:39.543 に答える