Дружественные функции.
В С++ у вас есть три способа создать "друга" для шаблонного класса
Способ 1. Нешаблонная функция-друг для шаблонного классаtemplate <typename T>
class C
{
T a;
friend void foo(C &c) // или `foo(C<T> &c)` - то же самое
{
c.a = 42;
}
};
Каждый раз, когда вы будете инстанцировать шаблон класса
C для какого-то аргумента
T -
C<int>,
C<double> и т.д. - компилятор будет автоматически генерировать дружественную функцию
foo с соответствующим аргументом -
foo(C<int> &),
foo(C<double> &) и т.д. Ключевым моментом, на который при этом нужно обратить внимание, является то, что эти дружественные функции сами по себе
не являются шаблонными. Каждая такая функция - вполне конкретна и генерируется для уже известного фиксированного типа аргумента.
Такие "странные" сущности, которое сами по себе не являются шаблонами, но при этом автоматически порождаются в процессе инстанцирования других шаблонов, начиная с С++11 называются термином "templat
ed entities" или неформальным термином
temploid (темплоид? шаблоид?). В данном случае
foo - это не шаблон функции, а шаблоид :O)
Другим важным моментом является то, что такую дружественную функцию невозможно определить за пределами определения шаблона класса. Язык С++ не предоставляет для этого синтаксиса. Наивная попытка это сделать обычно выглядит примерно так
template <typename T>
class C
{
T a;
friend void foo(C &c);
};
template <typename T>
void foo(C<T> &c)
{
c.a = 42;
}
Однако такая попытка ошибочна. Результирующий код не будет компилироваться (ошибки линковки или ошибки нарушения доступа), ибо шаблон функции, определенный снаружи не имеет никакого отношения к той функции, которая объявлена в качестве friend внутри определения шаблона класса. Многие компиляторы сразу же выдадут вам соответствующее предупреждение на friend-объявление.
Способ 2. Шаблонная функция-друг для шаблонного класса. Перекрестная дружбаВ этом вариант мы сделаем
foo шаблоном функции и подружим этот шаблон с нашим шаблоном класса
template <typename T>
class C
{
T a;
template <typename U> friend void foo(C<U> &c);
};
template <typename U>
void foo(C<U> &c)
{
c.a = 42;
}
Вот это уже корректный вариант. В таком варианте шаблон функции
foo<> можно определять как внутри определения шаблона класса, так и снаружи - как вам больше нравится. В примере выше он определен снаружи.
Но обратите внимание, что в таком случае все варианты шаблона
foo<> дружат со всеми вариантами шаблона
C<>. То есть, например, функция
foo<int> имеет доступ к внутренностям класса
C<double>. В большинстве случаев это не проблема. Но если вас это раздражает, то у вас есть третий вариант.
Способ 3. Шаблонная функция-друг для шаблонного класса. Эксклюзивная дружбаЭто наиболее навороченный вариант, который как правило не стоит затраченных усилий, но тем не менее
template <typename> class C;
template <typename U> void foo(C<U> &);
template <typename T>
class C
{
T a;
friend void foo<>(C<T> &c);
};
template <typename U>
void foo(C<U> &c)
{
c.a = 42;
}
В этом варианте, например,
foo<int> имеет доступ только к внутренностям
C<int>, но не к внутренностям
C<double>. То есть каждый вариант
foo<> дружит только со "своим" вариантом
C<> (Кстати, в первом варианте дружба тоже является эксклюзивной.)