4

二項演算子を使用した次のクラスを考えてみましょう (operator+例として使用しています)。

struct B{};

template<class>
struct A{
    template<class BB>
    void operator+(BB const&) const{std::cout<<"member"<<std::endl;}
    template<class BB>
    friend void operator+(BB const&, A const&){std::cout<<"friend"<<std::endl;}
};

この二項演算子は、次の 2 つの異なる型で呼び出すことができます。

A<int> a;
B b;
a + b; // member
b + a; // friend

次に、A両面 ( a + a) で使用しようとすると、多くの奇妙なことが起こります。3 つのコンパイラは、同じコードに対して異なる答えを返します。

void operator+(A const&)いくつかのコンテキスト:構文が機能しない場合に SFINAE 関数を削除するためのテンプレートが必要なため、定義したくありません。また、私はしたくありませんtemplate<class BB, class AA> friend void operator(BB const&, AA const&)。sinceAはテンプレートであるため、インスタンス化が異なると、同じテンプレートの複数の定義が生成されます。

元のコードを続ける:

奇妙なこと # 1: gcc では、フレンドが優先されます。

a + a; // prints friend in gcc

メンバーが優先されることを期待していますが、メンバーが gcc を優先する方法はありますか?

奇妙なこと # 2: clang では、このコードはコンパイルされません。

a + a; // use of overload is ambiguous

これはすでに gcc と clang の間の矛盾を指摘していますが、どちらが正しいですか? gccのように動作させるclangの回避策は何ですか?

引数をより貪欲にしようとする場合、たとえば、いくつかの最適化を適用するために、転送参照を使用できます。

struct A{
    template<class BB>
    void operator+(BB&&) const{std::cout<<"member"<<std::endl;}
    template<class BB>
    friend void operator+(BB&&, A const&){std::cout<<"friend"<<std::endl;}
};

奇妙なこと #3:転送参照を使用すると、gcc で警告が表示されます。

a + a; // print "friend", but gives "warning: ISO C++ says that these are ambiguous, even though the worst conversion for the first is better than the worst conversion for the second:"

しかし、それでもコンパイルされます.gccまたは回避策でこの警告を消すにはどうすればよいですか? ケース 1 と同様に、メンバー関数を優先すると予想されますが、ここではフレンド関数が優先され警告が表示されます。

奇妙なこと # 4:転送参照を使用すると、clang でエラーが発生します。

a + a; // error: use of overloaded operator '+' is ambiguous (with operand types 'A' and 'A')

gcc と clang の間の不一致を指摘しているのはどれですか。この場合、どちらが正しいのでしょうか?

要約すると、私はこのコードが一貫して機能するようにしようとしています。私は本当に関数がフレンド関数に注入されることを望んでいます(無料のフレンド関数ではありません)。インスタンス化が異なると同じ関数の宣言が重複して生成されるため、非テンプレート引数が等しい関数を定義したくありません。


再生する完全なコードは次のとおりです。

#include<iostream>
using std::cout;
struct B{};

template<class>
struct A{
    template<class BB>
    void operator+(BB const& /*or BB&&*/) const{cout<<"member\n";}
    template<class BB>
    friend void operator+(BB const& /*or BB const&*/, A const&){cout<<"friend\n";}
};

int main(){
    A<int> a;      //previos version of the question had a typo here: A a;
    B b;
    a + b; // calls member
    b + a; // class friend
    a + a; // surprising result (friend) or warning in gcc, hard error in clang, MSVC gives `member` (see below)

    A<double> a2; // just to instantiate another template
}

注: と を使用clang version 6.0.1してg++ (GCC) 8.1.1 20180712います。Francis Cugler MSVS 2017 CE によると、さらに異なる動作が得られます。


a+aclang と gcc (MSVS の場合) の両方で正しいこと (大文字と小文字を区別して「メンバー」を出力) を行う回避策を見つけましたが、ボイラー プレートと人工的な基本クラスには多くのものが必要です。

template<class T>
struct A_base{
    template<class BB>
    friend void operator+(BB const&, A_base<T> const&){std::cout<<"friend"<<std::endl;}
};

template<class T>
struct A : A_base<T>{
    template<class BB>
    void operator+(BB const&) const{std::cout<<"member"<<std::endl;}
};

ただし、に置き換えるBB const&と、あいまいな呼び出しが行われBB&&ます。

4

3 に答える 3

2

これらはすべてあいまいです。https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66914のように、メンバーと非メンバーを順序付けする場合、GCC には既知の部分順序付けバグがあります。

BBが の特殊化である場合は、友人がオーバーロードの解決に参加しないように制限してくださいA

于 2018-10-14T09:27:18.470 に答える