Рассмотрим следующий класс с бинарным оператором (я использую 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, я ожидаю pобратитесь к функции-члену, но здесь она предпочитает функцию друга , а выдает предупреждение.
Странная вещь # 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
.Согласно Фрэнсису Куглеру MSVS 2017 CE дает другое поведение.
Я нашел обходной путь, который делает правильную вещь (печатает 'member' для 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&
на BB&&
.