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;}
};

我可以用两种不同的类型调用这个二元运算符:

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

然后,当我尝试A在两边都使用 ( a + a) 时,会发生很多奇怪的事情。三个编译器对相同的代码给出不同的答案。

一些上下文:我不想定义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 之间的不一致,谁是对的? 让clang像gcc一样工作的解决方法是什么?

如果我尝试在参数中更加贪婪,例如应用一些优化,我可以使用转发引用:

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.1and g++ (GCC) 8.1.1 20180712。根据 Francis Cugler 的说法,MSVS 2017 CE 给出了不同的行为。


我找到了一种解决方法,可以为 clang 和 gcc(对于 MSVS?)执行正确的操作(为 case 打印 'member' a+a),但是对于样板和人工基类需要很多:

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

如果 BB 可转换为 A,您想禁用好友:

template<
  class BB,
  std::enable_if<
    !std::is_convertible<B const&, A>::value, int>::type = 0>
friend void operator+(BB const&, A const&){std::cout<<"friend"<<std::endl;}

注意:您需要使用std::enable_if作为类型来制作它,以便在 SFINAE 未解析时函数声明永远不会解析。

另一个提示是,如果您想解析成员函数,如果 BB 可以转换为 A(而不等于 A),您可能需要为模板提供默认类型:

template <typename BB = A>
void operator++(BB const&) const {/*...*/}

仅当您向该类提供其他运算符 ++ 时,这才真正有用,但值得注意。

于 2018-10-19T20:59:23.277 回答
2

这些都是模棱两可的。在对成员和非成员进行排序时,GCC 中存在已知的部分排序错误,例如https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66914

如果BBA.

于 2018-10-14T09:27:18.470 回答
1

我在 Visual Studio 2017 CE 中运行了您的代码:

int main(){
    A<int> a;
    B b;
    a + b; // calls member
    b + a; // class friend
    a + a; // surprising result or warning in gcc, hard error in clang
}

并且 Visual Studio 编译没有错误,并成功运行而没有警告,当程序返回时,它退出并(0)带有以下输出的代码:

member
friend
member

我已经用 float、double、char 尝试过这个并得到了相同的结果。

即使作为一个额外的测试,我在上面的模板类之后添加了这个:

/* your code here */

struct C {};

int main() {
    A<C> a;
    B b;
    a + b;
    b + a;
    a + a;

    return 0;
}

并且仍然得到相同的结果。


至于你的问题的后半部分Strange thing #1, #2, #3, #4:gcc&clang

我在 Visual Studio 中没有这个问题,因为a + a它给了我一个member输出,并且该成员优先于朋友重载。现在,至于运算符优先级的事实,我不知道是否GCCClang有所不同,Visual Studio因为每个编译器的工作方式都不同,我对它们并不熟悉,但至于语言本身,你的编译器不知道A::+()当它不知道<type>要使用哪种类型时该怎么办。A<int>::+()但是,当您拥有orA<char>::+()A<C>::+()...时,它知道该怎么做

于 2018-10-13T17:34:08.170 回答