Устранение неоднозначности друга и члена бинарного оператора - PullRequest
0 голосов
/ 13 октября 2018

Рассмотрим следующий класс с бинарным оператором (я использую 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&&.

Ответы [ 3 ]

0 голосов
/ 14 октября 2018

Это все неоднозначно.Известны ошибки частичного упорядочения в GCC при упорядочении члена и не члена, например https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66914.

Просто ограничьте вашего друга, чтобы он не участвовал в разрешении перегрузки, если BB является специализацией A.

0 голосов
/ 19 октября 2018

Вы хотите отключить друга, если BB конвертируется в A:

template<
  class BB,
  std::enable_if<
    !std::is_convertible<B const&, A>::value, int>::type = 0>
friend void operator+(BB const&, A const&){std::cout<<"friend"<<std::endl;}

Примечание: вам нужно использовать std::enable_if в качестве типа, чтобы сделать так, чтобы функция объявление никогда не разрешается, если SFINAE не разрешается.

Еще одна подсказка: если вы хотите разрешить функцию-член, если BB можно преобразовать в A (а не в A), вы можете указатьтип по умолчанию для шаблона:

template <typename BB = A>
void operator++(BB const&) const {/*...*/}

Это действительно очень полезно, если вы предоставляете классу другие операторы ++, но это стоит отметить.

0 голосов
/ 13 октября 2018

Я пошел и запустил ваш код в Visual Studio 2017 CE как:

int main(){
    A<int> a;
    B b;
    a + b; // calls member
    b + a; // class friend
    a + a; // surprising result or warning in gcc, hard error in clang
}

И Visual Studio скомпилировалась без ошибок и успешно запустилась без предупреждений, и когда программа вернула его, он завершился с кодом (0) с таким выводом:

member
friend
member

Я пробовал это с float, double, char и получил те же результаты.

Даже в качестве дополнительного теста я добавил это после класса шаблона выше:

/* your code here */

struct C {};

int main() {
    A<C> a;
    B b;
    a + b;
    b + a;
    a + a;

    return 0;
}

И все же получил те же результаты.


Что касается более поздней части вашего вопроса, которая относится к Strange thing #1, #2, #3, #4:, которая относится к обоим gcc & clang

У меня не было этой проблемы с Visual Studio, поскольку a + a давал мне member для вывода, и член имел приоритет над перегрузкой друга.Теперь, что касается факта приоритета операторов, я не знаю, в порядке вещей, будут ли GCC и Clang отличаться от Visual Studio, так как каждый компилятор работает по-разному, и я не очень знаком с ними, но что касаетсясам язык, который ваш компилятор не знает, что делать с A::+(), когда он не знает, какой тип <type> использовать.Однако он знает, что делать, когда у вас есть A<int>::+() или A<char>::+() или A<C>::+() ...

...