#c #language-lawyer
#c #g #clang #друг #неоднозначный вызов
Вопрос:
Рассмотрим следующий класс с двоичным оператором (я использую operator
только в качестве примера).
struct B{};
template<class>
struct A{
template<class BB>
void operator (BB constamp;) const{std::cout<<"member"<<std::endl;}
template<class BB>
friend void operator (BB constamp;, A constamp;){std::cout<<"friend"<<std::endl;}
};
Я могу вызвать этот двоичный оператор с двумя разными типами:
A<int> a;
B b;
a b; // member
b a; // friend
Затем, когда я пытаюсь использовать A
с обеих сторон ( a a
), происходит много странных вещей. Три компилятора дают разные ответы на один и тот же код.
Некоторый контекст: я не хочу определять void operator (A constamp;)
, потому что мне нужен шаблон для удаления функций SFINAE, если какой-то синтаксис не работает. Также я не хочу template<class BB, class AA> friend void operator(BB constamp;, AA constamp;)
. Поскольку since 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 (BBamp;amp;) const{std::cout<<"member"<<std::endl;}
template<class BB>
friend void operator (BBamp;amp;, A constamp;){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, я ожидаю, что предпочту функцию-член, но здесь она предпочитает функцию friend и выдает предупреждение.
Странная вещь # 4: использование ссылки на пересылку выдает ошибку в clang.
a a; // error: use of overloaded operator ' ' is ambiguous (with operand types 'A' and 'A')
Что снова указывает на несоответствие между gcc и clang, кто прав в этом случае?
В общем, я пытаюсь заставить этот код работать последовательно. Я действительно хочу, чтобы в функцию была введена функция friend (а не бесплатные функции friend). Я не хочу определять функцию с равными аргументами, отличными от шаблона, потому что разные экземпляры будут создавать дублированные объявления одних и тех же функций.
Вот полный код, с которым можно поиграть:
#include<iostream>
using std::cout;
struct B{};
template<class>
struct A{
template<class BB>
void operator (BB constamp; /*or BBamp;amp;*/) const{cout<<"membern";}
template<class BB>
friend void operator (BB constamp; /*or BB constamp;*/, A constamp;){cout<<"friendn";}
};
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
регистра) как для clang, так и для gcc (для MSV?), Но для этого требуется много для котельной плиты и искусственного базового класса:
template<class T>
struct A_base{
template<class BB>
friend void operator (BB constamp;, A_base<T> constamp;){std::cout<<"friend"<<std::endl;}
};
template<class T>
struct A : A_base<T>{
template<class BB>
void operator (BB constamp;) const{std::cout<<"member"<<std::endl;}
};
Однако он по-прежнему выдает неоднозначный вызов, если я заменяю BB constamp;
на BBamp;amp;
.
Ответ №1:
Все это неоднозначно. В GCC известны ошибки частичного упорядочения при упорядочении члена и нечлена, например https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66914 .
Просто ограничьте своего друга, чтобы он не участвовал в разрешении перегрузки, если BB
это специализация A
.
Комментарии:
1. Спасибо, но как вы делаете такое ограничение? Я попытался с помощью enable_if, но это не помогло, потому что ошибка возникает до устранения функции.
2. Эта ошибка указывает на дубликат gcc.gnu.org/bugzilla/show_bug.cgi?id=53499 , это примерно с 2012 года. Кажется, что особого интереса нет. Я бы сказал, что эта функция имеет фундаментальное значение для правильной реализации алгебраических типов.
Ответ №2:
Вы хотите отключить friend, если BB преобразуется в:
template<
class BB,
std::enable_if<
!std::is_convertible<B constamp;, A>::value, int>::type = 0>
friend void operator (BB constamp;, A constamp;){std::cout<<"friend"<<std::endl;}
Примечание: вам нужно использовать в std::enable_if
качестве типа, чтобы сделать так, чтобы объявление функции никогда не разрешалось, если SFINAE не разрешает.
Еще один совет: если вы хотите разрешить функцию-член, если BB легко преобразуется в A (и не равно A), вы можете указать тип по умолчанию для шаблона:
template <typename BB = A>
void operator (BB constamp;) const {/*...*/}
Это действительно полезно только в том случае, если вы предоставляете классу другие операторы opterator , но это стоит отметить.
Комментарии:
1. Вы бы ограничили функцию friend или функцию-член?
2. Это зависит от того, что вы хотите выбрать. Я предполагаю, что вы захотите использовать use the member, когда BB==A , поэтому вы хотите отключить функцию friend, но это полностью зависит от вас :-). Еще один совет: вы можете захотеть, чтобы функция-член была шаблоном <class BB=A> , что поможет функции-члену разрешить, если BB можно преобразовать в A. Я добавлю это в свою правку.
3. Спасибо, я думал в случае, когда общий эффект friend и member одинаков. То есть случай, представляющий собой арифметическую операцию, в которой оба имеют одинаковый конечный эффект, но все же компиляторы требуют разрешения неоднозначности.
4. Что касается вашего редактирования, я не уверен, помогает ли аргумент шаблона по умолчанию с выводом или для устранения двусмысленности.
5. (другими словами, печальная часть этого обходного пути заключается в том, что я даже не хочу менять поведение, а просто устраняю двусмысленность. Я не знаю, почему функция-член не была бы предпочтительнее на данный момент и была бы выполнена с ней, как, по-видимому, MSVS, согласно другому ответу).
Ответ №3:
Я пошел и запустил ваш код в 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
amp; clang
У меня не было этой проблемы с Visual Studio, поскольку a a
я выдавал мне a member
для вывода, и член имел приоритет над перегрузкой friend. Теперь, что касается факта приоритета оператора, я не знаю, насколько GCC
и Clang
будет отличаться от, Visual Studio
поскольку каждый компилятор работает по-разному, и я с ними не знаком, но что касается самого языка, ваш компилятор не знает, что делать, A:: ()
когда он не знает, что <type>
делатьиспользуйте. Однако он знает, что делать, когда у вас есть A<int>:: ()
или A<char>:: ()
или A<C>:: ()
…
Комментарии:
1. Да, извините, он не скопировал
<int>
inA<int>
в примере. Я исправил это сейчас, так что вы можете удалить первую часть своего ответа. В любом случае MSVC демонстрирует третье поведение (и то, которое мы ожидали).2. Я изменил ваш ответ, чтобы удалить ту часть, которая была вызвана моей ошибкой.
3. @alfC Не проблема и спасибо за помощь!