消除朋友和成员二进制运算符的歧义 [英] Disambiguation of friend and member binary operator

查看:225
本文介绍了消除朋友和成员二进制运算符的歧义的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

使用二进制运算符考虑以下类(我仅以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.1g++ (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.

如果BBA的特化,请约束您的朋友不要参加重载解决方案.

Just constrain your friend to not participate in overload resolution if BB is a specialization of A.

这篇关于消除朋友和成员二进制运算符的歧义的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆