У меня есть шаблон с перегруженным оператором друзей. Он работает хорошо, но если в области видимости есть другой несвязанный, но похожий оператор, он не компилируется: g ++ выдает странную ошибку, а схожие ошибки выдают icc и MSVC.
Код является:
template <class Type> class product {};
template <> class product<double> { public: typedef double type; };
template<class Type> class product2 {
public: typedef typename product<Type>::type type;
};
//------------
template <class Cmpt> class Tensor { };
template <class Cmpt>
typename product2<Cmpt>::type operator&
(const Tensor<Cmpt>& a, const Tensor<Cmpt>& b)
{ return 0; } // [1]
//template <class Cmpt>
//typename product<Cmpt>::type operator&
//(const Tensor<Cmpt>& a, const Tensor<Cmpt>& b)
//{ return 0; }
//-----
template<class Type> class fvMatrix;
template<class Type>
fvMatrix<Type> operator&
(const fvMatrix<Type>& a, const fvMatrix<Type>& b)
{ return a; }
template <class Type> class fvMatrix {
friend fvMatrix<Type> operator& <Type>
(const fvMatrix<Type>& a, const fvMatrix<Type>& b);
};
//----------
int main() {
fvMatrix<int> m;
m & m;
return 0;
}
Ошибка по gcc 4.8.1 (аналогично для 4.8.0 и 4.7.2):
c.cpp: In instantiation of 'class product2<int>':
c.cpp:13:31: required by substitution of 'template<class Cmpt> typename product2<Type>::type operator&(const Tensor<Cmpt>&, const Tensor<Cmpt>&) [with Cmpt = int]'
c.cpp:32:27: required from 'class fvMatrix<int>'
c.cpp:39:17: required from here
c.cpp:5:50: error: no type named 'type' in 'class product<int>'
public: typedef typename product<Type>::type type;
Подобные ошибки (то есть, пытаясь использовать product<int>::type
с помощью operator&
за Tensor<int>
) производятся ICC и MSVC.
Если я изменю код так, чтобы product
используется вместо или product2
в operator&
за Tensor
(раскомментируйте закомментированные строки и оператор комментария [1]), код компилируется.
Если я полностью уберу класс Tensor
с этими operator&
код компилируется.
ОБНОВИТЬ: полностью удаляя m&m;
строка по-прежнему оставляет код не компилируемым.
Я вижу, что многие источники предлагают писать friend fvMatrix<Type> operator& <>
то есть без Type
между <>
(http://www.parashift.com/c++-faq-lite/template-friends.html, Перегрузка оператора друга в шаблоне C ++), и это действительно решает эту проблему.
Тем не менее, даже обзор на https://stackoverflow.com/a/4661372/3216312 использования friend std::ostream& operator<< <T>
Итак, вопрос: почему приведенный выше код не компилируется? Пишет friend fvMatrix<Type> operator& <Type>
неправильно и почему?
Справочная информация: мы модифицируем среду OpenFOAM и столкнулись с такой проблемой в оригинальном коде OpenFOAM, который использует friend ... operator& <Type>
(http://foam.sourceforge.net/docs/cpp/a04795_source.html, строка 484).
Ваш friend
объявление соответствует первому из четырех пунктов из [temp.friend]/1
(остальные 3 пункта опущены):
14.5.4 Друзья [temp.friend]
1 Другом класса или шаблона класса может быть шаблон функции или
шаблон класса, специализация шаблона функции или класс
шаблон или обычная (не шаблонная) функция или класс. Для
Объявление функции друга, которое не является объявлением шаблона:— если
имя друга — это квалифицированный или неквалифицированный идентификатор шаблона,
объявление друга относится к специализация шаблона функции,
иначе
Какие имена будут найдены в декларации вашего друга?
7.3.1.2 Определения членов пространства имен [namespace.memdef]
3 […] Если имя в объявлении друга ни квалифицированный, ни
Шаблон-идентификатор и объявление является функцией или
разработанный тип-спецификатор, поиск, чтобы определить, является ли объект
ранее заявленный не рассматривает какие-либо области за пределами
внутреннее пространство имен. [ Заметка: Другие формы друга
декларации не может объявить нового члена самого внутреннего вложения
пространство имен и, таким образом, следовать обычным правилам поиска. — конец примечания]
Потому что у вас есть несколько перегрузок operator&
Частичный заказ обязателен:
14.5.6.2 Частичное упорядочение шаблонов функций
[Temp.func.order]
1 Если шаблон функции перегружен, использование шаблона функции
специализация может быть неоднозначной, потому что вычет аргумента шаблона
(14.8.2) может связать специализацию шаблона функции с более
чем одно объявление шаблона функции. Частичное упорядочение перегруженных
объявления шаблона функции используются в следующих контекстах для
выберите шаблон функции, к которому относится шаблон функции
специализация относится:— когда функция друга
декларация (14.5.4), явный экземпляр (14.7.2) или
явная специализация (14.7.3) относится к шаблон функции
специализация.
и набор кандидатов, как обычно, определяется набором функций, которые выдерживают вычет шаблона аргумента:
14.8.2.6 Вывод аргументов шаблона из объявления функции [temp.deduct.decl]
1 В объявлении, идентификатор объявления которого относится к специализация
шаблон функции, вывод аргумента шаблона выполняется для
определить специализацию, к которой относится декларация.
В частности, это делается для явных реализаций (14.7.2),
явные специализации (14.7.3), и определенные заявления друзей
(14.5.4).
где удержание оставшегося аргумента регулируется печально известным предложением SFINAE (Ошибка субстрата не является ошибкой), которое применяется только к непосредственному контексту:
14.8.2
Вывод аргумента шаблона
[Temp.deduct]
8 […] Если подстановка приводит к неверному типу или выражению, вывод типа завершается неудачно. Недопустимый тип или выражение
тот, который был бы плохо сформирован, с необходимостью диагностики, если был написан с использованием замещенных аргументов. [ Заметка:
Если диагностика не требуется, программа все еще не работает. Проверка доступа выполняется как часть замены
процесс. — примечание конца] Только недопустимые типы и выражения в непосредственном контексте типа функции и
его типы параметров шаблона могут привести к ошибке вывода.
Во всех вариациях вашего сообщения поиск по аргументу найдет две перегрузки: operator&
в связанном глобальном пространстве имен fvMatrix
шаблон класса. Затем эти перегрузки должны играть вывод аргументов и частичное упорядочение:
friend ... operator& <Type> (...)
, нет аргумента вывода, но простая замена Cmpt=int
а также Type=int
, который дает неверный тип для product<int>::type
внутри product2
, Это не в непосредственном контексте и, следовательно, серьезная ошибка. Удаление Tensor
Шаблон класса, конечно, также удаляет ошибку.typename product<Cmpt>::type
вместо product2<Cmpt::type
как тип возвращаемого значения operator&
на Tensor<Cmpt>
, Здесь недопустимый тип находится в непосредственном контексте, и вы получаете мягкую ошибку SFINAE и действительный operator&
за fvMatrix<Type>
выбран.friend ... operator& <> (...)
, Это требует вывода аргумента и теперь оригинал operator&
на Tensor
с product2::type
возвращаемый тип на самом деле безвреден, потому что сам вывод аргументов завершается неудачей (нет шаблона Cmpt
что может сделать Tensor<Cmpt>
равно fvMatrix<int>
) и нет подстановки, которая может привести к серьезной ошибке. Поскольку основной причиной является загрязнение глобального пространства имен несвязанными перегрузками операторов, лечение простое: обернуть каждый шаблон класса в своем собственном пространстве имен! Например. Tensor<Cmpt>
в namespace N1
а также fvMatrix<Type>
в namespace N2
, Тогда объявление друга внутри fvMatrix
не найдет operator&
за Tensor<Cmpt>
и все работает отлично.
Из того, что я могу сказать, первые две строчки
template <class Type> class product {};
template <> class product<double> { public: typedef double type; };
следует заменить на
template <class Type> class product { public: typedef Type type; };
Я не совсем уверен, если это то, что вы хотите, но это избавляет от ошибки компилятора.
Принципиально ошибка
c.cpp:5:50: error: no type named 'type' in 'class product<int>'
был вызван тем, что определение для product<int>
пустой.
Обратите внимание, что ваша первая строка определяет общее product<Type>
это пусто, а вторая строка определяет только
public: typedef double type;
как тело product<double>
не для генерала product<Type>
,
Новая версия этого позволяет аналогичную typedef
быть созданным для всех типов.
Как примечание стороны, если вы измените
fvMatrix<int> m;
в
fvMatrix<double> m;
код также компилируется, потому что product<double>
содержит public: typedef double type;
,