Функция друга шаблона шаблонного класса
Я боролся с проблемой, описанной в этом вопросе (объявление функции шаблона как друга класса шаблона), и я считаю, что 2-й ответ - это то, что я хочу сделать (объявить функцию шаблона вперед, а затем назвать специализацию другом). У меня есть вопрос о том, является ли немного другое решение действительно правильным или просто работает в Visual C++ 2008.
Тестовый код:
#include <iostream>
// forward declarations
template <typename T>
class test;
template <typename T>
std::ostream& operator<<(std::ostream &out, const test<T> &t);
template <typename T>
class test {
friend std::ostream& operator<< <T>(std::ostream &out, const test<T> &t);
// alternative friend declaration
// template <typename U>
// friend std::ostream& operator<<(std::ostream &out, const test<T> &t);
// rest of class
};
template <typename T>
std::ostream& operator<<(std::ostream &out, const test<T> &t) {
// output function defined here
}
Во-первых, одна странная вещь, которую я обнаружил, заключалась в том, что если я изменю предварительную декларацию operator<<
чтобы оно не совпадало (например, std::ostream& operator<<(std::ostream &out, int fake);
все по-прежнему компилируется и работает правильно (для ясности, мне не нужно определять такую функцию, только объявить ее). Однако, как и в связанном вопросе, удаление объявления forward вызывает проблему, поскольку компилятор, похоже, считает, что я объявляю элемент данных вместо функции Friend. Я почти уверен, что такое поведение является ошибкой Visual C++ 2008.
Интересно, когда я удаляю предварительные объявления и использую альтернативное объявление друга в приведенном выше коде. Обратите внимание, что параметр шаблона U
не появляется в следующей подписи. Этот метод также компилируется и работает правильно (без изменения чего-либо еще). Мой вопрос заключается в том, соответствует ли это стандарту или особенность Visual C++ 2008 (я не смог найти хорошего ответа в своих справочниках).
Обратите внимание, что в то время как объявление друга template <typename U> friend ... const test<U> &t);
также работает, это на самом деле дает каждый экземпляр оператора friend
доступ к любому экземпляру test
в то время как я хочу, чтобы частные члены test<T>
должен быть доступен только из operator<< <T>
, Я проверил это, создав test<int>
внутри operator<<
и доступ к частному члену; это должно вызвать ошибку компиляции, когда я пытаюсь вывести test<double>
,
Сводка: удаление предварительных объявлений и переключение на альтернативное объявление друга в приведенном выше коде, похоже, дает тот же результат (в Visual C++ 2008) - действительно ли этот код является правильным?
ОБНОВЛЕНИЕ: Любая из указанных выше модификаций кода не работает в gcc, поэтому я предполагаю, что это ошибки или "особенности" в компиляторе Visual C++. Тем не менее я был бы признателен за идеи от людей, знакомых со стандартом.
1 ответ
... если я изменю предварительное объявление оператора <<, чтобы оно не совпадало
Другую функцию следует рассматривать как особый тип объявления. По сути, компилятор делает достаточно для анализа объявления, однако семантическая проверка не будет выполняться, если вы на самом деле не специализируете класс.
После внесения предложенного вами изменения, если вы создадите экземпляр test
вы получите ошибку о несоответствии объявлений:
template class test<int>;
... Однако... удаление предварительного объявления вызывает проблему
Компилятор пытается проанализировать объявление, чтобы сохранить его, пока шаблон класса не станет специализированным. Во время разбора компилятор достигает <
в декларации:
friend std::ostream& operator<< <
Единственный способ, которым operator<<
может сопровождаться <
если это шаблон, то выполняется поиск, чтобы убедиться, что это шаблон. Если шаблон функции найден, то <
считается началом шаблонных аргументов.
При удалении предварительной декларации шаблон не найден и operator<<
считается объектом. (Это также почему, когда вы добавляете using namespace std
код продолжает компилироваться, так как должны быть объявления шаблонов для operator<<
).
... когда я удаляю предварительные объявления и использую альтернативное объявление друга в приведенном выше коде. Обратите внимание, что параметр шаблона U не отображается в следующей подписи...
Не требуется, чтобы все параметры шаблона использовались в аргументах шаблона функции. Альтернативное объявление предназначено для нового шаблона функции, который будет вызываться только в том случае, если он объявлен в пространстве имен и задан явный аргумент шаблона.
Простой пример этого будет:
class A {};
template <typename T> A & operator<<(A &, int);
void foo () {
A a;
operator<< <int> (a, 10);
}
... этот код на самом деле правильный?..
Ну, есть две части этого. Во-первых, альтернативная функция Friend не ссылается на объявление позже в области видимости:
template <typename T>
class test {
template <typename U>
friend std::ostream& operator<<(std::ostream &out, const test<T> &t);
};
template <typename T>
std::ostream& operator<<(std::ostream &out, const test<T> &t); // NOT FRIEND!
Функция Friend будет фактически объявлена в пространстве имен для каждой специализации:
template <typename U>
std::ostream& operator<<(std::ostream &out, const test<int> &t);
template <typename U>
std::ostream& operator<<(std::ostream &out, const test<char> &t);
template <typename U>
std::ostream& operator<<(std::ostream &out, const test<float> &t);
Каждая специализация operator<< <U>
будет иметь доступ к конкретной специализации в соответствии с типом ее параметра test<T>
, Таким образом, по сути доступ ограничен, как вам требуется. Однако, как я упоминал ранее, эти функции в основном не могут использоваться в качестве операторов, так как вы должны использовать синтаксис вызова функции:
int main ()
{
test<int> t;
operator<< <int> (std << cout, t);
operator<< <float> (std << cout, t);
operator<< <char> (std << cout, t);
}
Согласно ответам на предыдущий вопрос, вы либо используете предварительную декларацию, предложенную litb, либо продолжаете определять встроенную функцию друга в соответствии с ответом Dr_Asik (что, вероятно, было бы тем, что я бы сделал).
ОБНОВЛЕНИЕ: 2-й комментарий
... изменение предварительного объявления перед классом; тот в классе все еще соответствует функции, которую я реализую позже...
Как я уже говорил выше, компилятор проверяет, operator<<
шаблон, когда он видит <
в декларации:
friend std::ostream& operator<< <
Это делается путем поиска имени и проверки, является ли это шаблоном. Пока у вас есть фиктивная предварительная декларация, этот компилятор "обманывает" компилятора так, чтобы он рассматривал вашего друга как имя шаблона, и поэтому <
считается началом списка аргументов шаблона.
Позже, когда вы создаете экземпляр класса, у вас есть действительный шаблон для соответствия. По сути, вы просто обманываете компилятор, рассматривая его как специализацию шаблона.
Вы можете сделать это здесь, потому что (как я уже говорил ранее) в этот момент времени семантическая проверка не выполняется.