考虑以下带有二元运算符的类(我使用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 一样,我希望更喜欢成员函数,但这里更喜欢友元函数and发出警告。
奇怪的事情#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
and g++ (GCC) 8.1.1 20180712
。根据 Francis Cugler MSVS 2017 CE 的说法,CE 给出了不同的行为。
我找到了一种解决方法,可以做正确的事情(打印“成员”a+a
case)对于 clang 和 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&
with BB&&
.