8

clang-3.4 (git からコンパイル) を試してみると、私のプロジェクトの 1 つをコンパイルできず、オーバーロードされた演算子を解決する際にあいまいさを訴えていました。テンプレート化された 2 つの演算子があり、そのうちの 1 つはメンバー関数として宣言され、もう 1 つは非メンバー関数として宣言されていました。

次の SSCCE は状況を示しています。

#include <iostream>

struct ostr {
        std::ostream& s;

        template<class T>
        ostr& operator<<(const T& x) { s << x; return *this; }
};

struct xy {
        double x, y;
};

template<class Stream>
Stream& operator<<(Stream& s, const xy& x) {
        s << "[" << x.x << ", " << x.y << "]";
        return s;
}

int main() {
        ostr os{std::cout};
        xy x{4, 5};
        os << "Value is: " << x <<"\n";
}

プロジェクトは以前に正常にコンパイルされましたが、この SSCCE をいくつかのコンパイラ ( gcc 4.54.64.74.8およびclang 3.3) で再度チェックしましたが、すべてのコンパイラが警告なしでコンパイルしました ( を使用-Wall -Wextra -pedantic)。すべてのコンパイラは C++11/C++0x 標準に設定されました。ctor を に追加した後、および)ostrでも正常にコンパイルされましたMSVC 20122010

両方をoperator<<非メンバーにすることは、すべてのコンパイラであいまいさを示します(予想どおり)

標準ドラフト (N3242およびN3690) を調べた後、メンバー関数/演算子を非メンバー関数/演算子よりも適切に一致させるものを見つけることができませんでした。

だから私は間違っていることを証明できなかったし、clang-3.4誰が正しいのだろうか. したがって、私の質問は次のとおりです。

  • このコードは有効ですか? メンバーの演算子/関数は、非メンバーのものよりも適切に一致する必要がありますが、これは clang-3.4 のバグですか?
  • それとも、他のすべてのコンパイラが間違っている/寛容すぎるのでしょうか?

operator<<2 番目の関数をテンプレート化されていない関数 (std::ostreamテンプレート パラメーターの代わりに使用) に変更すると、あいまいさが解決され、期待どおりに機能することは承知していますが、それはここでのポイントではありません。

4

1 に答える 1

4

オーバーロードの解決は、オーバーロードの解決のみを目的として、追加のパラメーターをメンバー関数に追加します。

[over.match.funcs]/2

候補関数のセットには、同じ引数リストに対して解決されるメンバー関数と非メンバー関数の両方を含めることができます。引数とパラメーターのリストがこの異種セット内で比較できるように、メンバー関数は、メンバー関数が呼び出されたオブジェクトを表す暗黙的なオブジェクト パラメーターと呼ばれる追加のパラメーターを持つと見なされます。

/4

非静的メンバー関数の場合、暗黙的なオブジェクト パラメーターの型は次のとおりです。

— ref-qualifier なしで、またはref -qualifierを使用して宣言された関数の「cv への左辺値参照」X&

— ref-qualifierで宣言された関数の「cv への右辺値参照」X&&

ここXで、 は関数がメンバーであるクラスで、cvはメンバー関数宣言のcv修飾です。

たとえば、右辺値をこの暗黙的なオブジェクト パラメーターにバインドできるようにするなど、いくつかの特別な規則に従います (右辺値の参照修飾子なしでメンバー関数を呼び出す場合などostr{std::cout}<<"hello")。


オーバーロード解決のために比較する必要がある暗黙的なオブジェクト パラメーターを含む関数シグネチャは次のとおりです。

template<class T>
ostr& ostr::operator<<(ostr&, const T&);    // F1

template<class Stream>
Stream& ::operator<<(Stream&, const xy&);    // F2

を置換するとos << x同じ署名が得られます。

ostr& ostr::operator<<(ostr&, const xy&);
ostr& ::    operator<<(ostr&, const xy&);

したがって、[over.match.best]/1 の「タイブレーカー」の 1 つだけが曖昧さを解決できます。実際、「」F1は「」よりも特殊化されてF2いる (またはその逆) という、関数テンプレートの部分的な順序付けを適用できます。

注: 暗黙のオブジェクト パラメータを追加する手順は、半順序付け [temp.func.order]/3 の説明で再度指定されています。


F1andの半順序付けF2(上記で定義) の場合、最初に 2 つの一意の型を作成します。

struct unique_T {};
struct unique_Stream {};

次に、テンプレート パラメータを一意の型に置き換えることで に変換F1します(同様に についても):F1'Tunique_TF2

ostr& ostr::operator<<(ostr&, const unique_T&);
ostr& ::    operator<<(unique_Stream&, const xy&);

変換された関数のパラメーターは、変換F1'されていない のテンプレート パラメーターを推測するために使用されますF2

ostr a0;
unique_T a1; // no reference, no cv-qualifier
::operator<<(a0, a1) // does template argument deduction succeed?

// reminder: signature of ::operator<<
template<class Stream>
Stream& ::operator<<(Stream&, const xy&);

a0[with Stream= ostr]の演繹は成功するため、 ostr&fromの型F1は、少なくともF2(テンプレート パラメーターである)Stream&の対応する最初のパラメーターの型と同じくらい特殊化されていると見なされます。の 2 番目のパラメーター(タイプは)に対して控除が行われないためStream、2 番目の引数 がどうなるかわかりません。a1::operator<<const xy&

ここで、 からの引数を使用してプロセスを繰り返し、F2'のテンプレート パラメータを推定しようとしますF1

unique_Stream a0;
xy a1;
ostr::operator<<(a0, a1);

// reminder: signature of ostr::operator<<
template<class T>
ostr& ostr::operator<<(ostr&, const T&);

ここでは、第 1 引数の控除は発生しませんが、第 2 引数の [with T= xy] で控除が発生し、成功します。

私は、これほど特殊化された関数テンプレートはないと結論付けています。したがって、あいまいさのためにオーバーロードの解決は失敗するはずです。

于 2013-11-13T15:45:09.893 に答える