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
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.
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.
Continuing with the original code:
Strange thing # 1: In gcc, the friend takes precedence:
a + a; // prints friend in gcc
I expect the member to take precedence, is there a way for the member to take precedence gcc?
Strange thing # 2: In clang, this code doesn't compile:
a + a; // use of overload is ambiguous
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;}
};
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:"
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.
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')
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
}
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.
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;}
};
However it still give an ambiguous call if I replace BB const&
with BB&&
.
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.
Just constrain your friend to not participate in overload resolution if BB
is a specialization of A
.