使用二进制运算符考虑以下类(我以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&)
。因为A
是模板,所以不同的实例化将产生同一模板的多个定义。
继续原始代码:
奇怪的事情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.1
和g++ (GCC) 8.1.1 20180712
。根据弗朗西斯·库格勒(Francis Cugler)的说法,MSVS 2017 CE的行为有所不同。
我发现了一种变通办法,可以对clang和gcc(对于MSVS?)都做正确的事情(在a+a
情况下打印'member'),但是对于样板和人工基类则需要很多工作:
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&&
,它仍然会发出歧义。
答案 0 :(得分: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
的输出,并且该成员优先于朋友重载。现在,关于运算符优先级的事实,我不知道GCC
和Clang
是否会与Visual Studio
不同,因为每个编译器的工作方式都不一样,我不是真的熟悉它们,但至于语言本身,编译器在不知道要使用哪种A::+()
的情况下也不知道如何处理<type>
。但是,如果您拥有A<int>::+()
或A<char>::+()
或A<C>::+()
...
答案 1 :(得分:1)
这些都是模棱两可的。在订购成员和非成员时,例如在GCC中,GCC中存在部分已知的订购错误。 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66914。
如果BB
是A
的特长,请约束您的朋友不要参加重载解决方案。
答案 2 :(得分:1)
如果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无法解析时函数 declaration 永远不会解析。
另一个提示是,如果BB可转换为A(且不等于A),并且要解析为成员函数,则可能需要为模板提供默认类型:
template <typename BB = A>
void operator++(BB const&) const {/*...*/}
只有在为该类提供其他opterator ++时,这才真正有用,但是值得注意。