消除朋友和成员二进制运算符的歧义 [英] Disambiguation of friend and member binary operator
问题描述
使用二进制运算符考虑以下类(我仅以operator+
为例).
Consider the following class with a binary operator (I use operator+
just as an example).
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;}
};
我可以用两种不同的类型来调用此二进制运算符:
I can call this binary operator with two different types:
A<int> a;
B b;
a + b; // member
b + a; // friend
然后,当我尝试在两面同时使用A
(a + a
)时,会发生很多奇怪的事情.三个编译器对相同的代码给出不同的答案.
Then when I try to use A
on both sides (a + a
) a lot of strange things happen. Three compilers give different answer to the same code.
某些上下文:我不想定义void operator+(A const&)
,因为如果某些语法不起作用,我需要一个模板来使用SFINAE函数.另外,我也不需要template<class BB, class AA> friend void operator(BB const&, AA const&)
.因为A
是模板,所以不同的实例化将产生同一模板的多个定义.
Some context: I don't want to define void operator+(A const&)
because I need a template to SFINAE functions away if some syntax doesn't work. Also I don't want a template<class BB, class AA> friend void operator(BB const&, AA const&)
. Because since A
is a template, different instantiations will produce multiple definitions of the same template.
继续原始代码:
奇怪的事情1:在gcc中,朋友优先:
Strange thing # 1: In gcc, the friend takes precedence:
a + a; // prints friend in gcc
我希望该成员优先,有一种方法让该成员优先gcc?
奇怪的事情#2:在Clang中,此代码无法编译:
Strange thing # 2: In clang, this code doesn't compile:
a + a; // use of overload is ambiguous
这已经指出了gcc和clang之间的不一致,谁是对的? 对clang来说,使其像gcc一样工作的解决方法是什么?
This already points out at an inconsistency between gcc and clang, who is right? What would be a workaround for clang that makes it work like gcc?
如果我尝试在参数上更贪婪,例如要进行一些优化,我可以使用转发引用:
If I try to be more greedy in the arguments, e.g. to apply some optimization I could use forwarding references:
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中发出警告,
Strange thing # 3: Using forwarding reference gives a warning in 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一样,我希望使用成员函数,但在这里它希望使用朋友函数, 发出警告.
But still compiles, How can I silence this warning in gcc or workaround? Just as in case # 1, I expect to prefer the member function but here it prefers the friend function and gives a warning.
奇怪的事情#4:使用转发引用会导致clang错误.
Strange thing # 4: Using forwarding reference gives an error in clang.
a + a; // error: use of overloaded operator '+' is ambiguous (with operand types 'A' and 'A')
谁又指出了gcc和clang之间的不一致,在这种情况下谁是正确的?
Which again points at an inconsistency between gcc and clang, who is right in this case?
总而言之,我正在尝试使此代码始终如一地工作.我真的希望该功能被注入朋友功能(不是免费的朋友功能).我不想用相等的非模板参数定义一个函数,因为不同的实例化将产生相同函数的重复声明.
In summary, I am trying to make this code work consistently. I really want the function to be injected friend function (not free friend functions). I don't want to define a function with equal non-template arguments because different instantiation will produce duplicated declarations of the same functions.
这是要使用的完整代码:
Here is the full code to play with:
#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的行为有所不同.
Note: I am using clang version 6.0.1
and g++ (GCC) 8.1.1 20180712
. According to Francis Cugler MSVS 2017 CE give yet a different behavior.
我发现了一种解决方法,可以对clang和gcc(对于MSVS?)执行正确的操作(在a+a
情况下打印'member'),但是对于样板和人工基类则需要很多操作:
I found a workaround that does the correct thing (prints 'member' for a+a
case) for both clang and gcc (for MSVS?), but it requires a lot for boiler plate and an artificial base class:
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&&
,它仍然会发出模棱两可的呼叫.
However it still give an ambiguous call if I replace BB const&
with BB&&
.
推荐答案
这些都是模棱两可的.在订购成员和非成员时,例如在GCC中,GCC中存在一些已知的部分订购错误. https://gcc.gnu.org/bugzilla/show_bug.cgi?id= 66914 .
These are all ambiguous. There are known partial ordering bugs in GCC when ordering a member and a non-member, e.g. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66914.
如果BB
是A
的特化,请约束您的朋友不要参加重载解决方案.
Just constrain your friend to not participate in overload resolution if BB
is a specialization of A
.
这篇关于消除朋友和成员二进制运算符的歧义的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!