11

How to make these std::function parameters unambiguous?という質問を読んだ後、私は完全に混乱しています。、これまでのところ、関数テンプレートの半順序付けとは何かを理解していると思っていましたが、その質問を読んだ後、コンパイラの動作を確認するために 3 つの例を書き留めましたが、受け取った結果は理解しにくいものです。

例 #1

template <class T>
void foo(T) {}

template <class T>
void foo(T&) {}

int main()
{
  int i;
  foo<int>(i); // error: call is ambiguous! 
}

質問:両方の関数が実行可能であることは明らかですが、T&より専門化されたものではありませんTか? 代わりに、コンパイラはあいまいな呼び出しエラーを発生させます。

例 #2

#include <iostream>

template <class T>
struct X {};

template <>
struct X<int> 
{
  X() {}
  X(X<int&> const&) {} // X<int> is constructible from X<int&>
  // note: this is not a copy constructor!
};

template <>
struct X<int&> 
{
  X() {}
  X(X<int> const&) {} // X<int&> is constructible from X<int>
  // note: this is not a copy constructor!
};

template <class T>
void bar(X<T>) { std::cout << __PRETTY_FUNCTION__ << std::endl; }

template <class T>
void bar(X<T&>) { std::cout << __PRETTY_FUNCTION__ << std::endl; }

int main()
{
  bar<int>(X<int>());   // calls void bar(X<T>) [with T = int]

  bar<int>(X<int&>());  // calls void bar(X<T&>) [with T = int]
}

質問:T&例 1 でandTがあいまいな場合、なぜここで none 呼び出しがあいまいなのですか? X<int>から構築可能でX<int&>あり、提供されたX<int&>コンストラクターのX<int>おかげで から構築可能です。コンパイラによって生成されたX<int>::X(X<int> const&)コピー コンストラクターが ,よりも優れた変換シーケンスX<int>::X(X<int&> const&)であるためでしょうか (そうであれば、何が優れているか、引数が値によって渡されることに注意してください)、特殊化の順序はまったく問題ではありませんか?

例 #3

#include <iostream>

// note: a new type used in constructors!
template <class U>
struct F {};

template <class T>
struct X
{
  X() {}

  template <class U>
  X(F<U> const&) {}  // X<T> is constructible from any F<U>
  // note: it takes F type, not X!
};

template <class T>
void qux(X<T>) { std::cout << __PRETTY_FUNCTION__ << std::endl; }

template <class T>
void qux(X<T&>) { std::cout << __PRETTY_FUNCTION__ << std::endl; }

int main()
{
  qux<int>(F<int>());  // calls void qux(X<T&>) [with T = int]

  qux<int>(F<int&>()); // calls void qux(X<T&>) [with T = int]
}

質問:これは、リンクされた質問からラムダ[](int){}std::function<void(int&)>および に一致させる」のと同様のシナリオです。std::function<void(int)>両方の呼び出しで、より特化した関数テンプレートが選択されるのはなぜですか? 変換シーケンスが同じなので、半順序が問題になり始めたからですか?

すべてのテストは GCC 4.9.0 で実行され-std=c++11、追加のフラグはありません。

4

2 に答える 2

7

オーバーロードの解決では、次のように最適な関数を見つけようとします。

(1) [over.match.best]/1:

これらの定義を考えると、実行可能な関数は、すべての引数 iに対してが よりも悪い変換シーケンスではない場合F1、別の実行可能な関数よりも優れた関数であると定義されます。そして— 一部の引数jについては よりも良い変換シーケンスです。そうではありません— コンテキストは、ユーザー定義の変換 (8.5、13.3.1.5、および 13.3.1.6 を参照) による初期化であり、戻り値の型から宛先の型 (つまり、対象となるエンティティの型) への標準の変換シーケンスです。初期化済み) は、戻り値の型から宛先の型への標準の変換シーケンスよりも優れた変換シーケンスです。[ 例:F2ICSi(F1)ICSi(F2)
ICSj(F1)ICSj(F2)
F1F2

struct A {
  A();
  operator int();
  operator double();
} a;

int i = a;    // a.operator int() followed by no conversion is better 
              // than `a.operator double()`     
              // followed by a conversion to `int`
float x = a;  // ambiguous: both possibilities require conversions,
              // and neither is better than the other

例の終了] または、そうでない場合
— F1 は非テンプレート関数であり、F2 は関数テンプレートの特殊化です。そうでない場合、
— F1 と F2 は関数テンプレートの特殊化であり、F1 の関数テンプレートはより特殊化されています。 14.5.6.2 で説明されている半順序規則に従って、F2 のテンプレートよりも。


ケース 1:

T&しかし、より専門的なものを取っているのではありませんTか?

オーバーロードの解決によると、変換なし (どちらも完全一致である ID 変換) の方が優れており、(1) の他の箇条書きは適用されないため、部分的な順序付けが行われます。[temp.deduct.partial]/5 は、参照が半順序付けのために参照する型に置き換えられることを示しています。

部分的な順序付けが行われる前に、部分的な順序付けに使用される型に対して特定の変換が実行されます。
Pが参照型の場合、参照される型にP置き換えられます。
Aが参照型の場合、参照される型にA置き換えられます。

パラメータ テンプレートのパラメータは完全に同一であるため、互いに対する推論が両方の方法で成功することを確認するのは難しくありません。したがって、どちらのテンプレートも他方よりも特殊化されていません。

ケース 2:

ここでは部分的な順序付けは必要ありません。X<int>からへのユーザー定義変換は、 への変換よりもX<int&>ランクが低くなります -- 後者には [over.ics.user]/4 によって完全一致ランクが与えられます。X<int>X<int>

クラス型の式を同じクラス型に変換すると、完全一致ランクが与えられます […]

したがって、変換ランクを持つ へX<int>の変換よりも明らかに優れた変換です。X<int&>逆も同様X<int&>ですX<int>

ケース 3:

3 番目のケースは最初のケースと似ています。X<int>両方ともX<int&>、 の任意の特殊化を取ることができるコンストラクター テンプレートを持っていFます。(1) は、どの変換シーケンスも他のものよりも優れているわけではないため (実際、それらは完全に同一です)、より特化したテンプレートが選択されることを示しています。

template <class T> void bar(X<T>);  // #1

template <class T> void bar(X<T&>); // #2

// Call:
bar<int>( F<int>() );

[temp.deduct.partial] に戻り、型推論を行います。Unique各引数テンプレートのテンプレート パラメーター用に、一意の型 (それを呼び出します) が合成されます。次の手順が実行され、対応する結果が得られます。手順はF<int>as with F<int&>(および のその他の特殊化F)を呼び出す場合とまったく同じであることに注意してください。

  1. テンプレート #1 はパラメータ テンプレートとして、テンプレート #2 は引数テンプレートとして、X<Unique&>に対して推定されX<T>、 が生成されT=Unique&ます。一方で、
  2. 引数テンプレートとしてのテンプレート #1 に対するパラメーター テンプレートとしてのテンプレート #2 は、X<Unique>に対して推定されX<T&>その結果、推定失敗.

ご覧のとおり、テンプレート #2 はより特殊化されています。手順 1 で引数テンプレートとして使用した場合、推論は成功しましたが、手順 2 で引数テンプレートとしてテンプレート #1 を使用した場合、推論は失敗しました。したがって、2 番目のより特殊化された関数テンプレートの特殊化が呼び出されます。

于 2014-10-11T19:16:46.707 に答える
1

例 1 :

コンパイラは、参照渡しか値渡しかを判断できませんa。テンプレートを で特殊化するとT *、関数呼び出しの構文が異なるため、彼は簡単に知ることができますfoo(&a)

例 2 :

quxここで、 の 2 番目のオーバーロードがを受け取ることをコンパイラに伝えてX<T &>T &. あいまいさはありません。しかし、これを行うと:

template <class T>
void qux(X<T>) { std::cout << __PRETTY_FUNCTION__ << std::endl; }

template <class T>
void qux(X<T> &) { std::cout << __PRETTY_FUNCTION__ << std::endl; }

あなたは同じ問題で終わるでしょう

例 3:

同じ問題。

それが非常に明確かどうかわからないので、誰かが私の答えを改善できれば、それは作者にとって役立つかもしれません

于 2014-10-11T19:05:40.570 に答える