АТ>Вариант 1
АТ>Наивная попытка объявить специализацию шаблонного метода специализации одного шаблонного класса другом другого шаблонного класса:
АТ>template <typename T> struct A
{
template <typename U> void foo(U u);
};
template <typename T> class B
{
friend void A<T>::foo<T>(T);
};
Говорят, это ОК.
Вариант 1
Наивная попытка объявить специализацию шаблонного метода специализации одного шаблонного класса другом другого шаблонного класса:
template <typename T> struct A
{
template <typename U> void foo(U u);
};
template <typename T> class B
{
friend void A<T>::foo<T>(T);
};
Обратите внимание, что во friend-объявлении я хочу явно указать параметр для шаблона функции. Возможно ли это? (Если я уберу `<T>` после `foo`, то все вопросы отпадут.)
Clang вплоть до версии 6 спокойно проглатывал такое объявление друга. GCC же всегда ругался:
error: invalid use of qualified-name 'A<T>::foo'
8 | friend void A<T>::foo<T>(T);
Однако более поздние версии Clang тоже начали ругаться на такой вариант
error: no candidate function template was found for dependent friend function template specialization
friend void A<T>::foo<T>(T);
^
Почесав немного репу, говорим себе: "Ага! Это же вложенный шаблон в шаблоне! А не попробовать ли нам добавить тут `template`?".
Вариант 2
template <typename T> struct A
{
template <typename U> void foo(U u);
};
template <typename T> class B
{
friend void A<T>::template foo<T>(T);
};
На этом этапе GCC молча принимает такие объявления, в то время как Clang продолжает жаловаться
error: 'template' keyword not permitted here
friend void A<T>::template foo<T>(T);
Радость с GCC тоже длится недолго: достаточно инстанцировать `B`
int main()
{
B<int> b;
}
как GCC тут же выдает сообщение несколько иного рода
error: 'foo' was not declared in this scope
8 | friend void A<T>::template foo<T>(T);
| ^~~~
на ту же строчку. Странное сообщение, но и так бывает.
То есть и так тоже не получается.
Посему вопрос: а как правильно? Существует ли вообще возможность такого объявления друга и правильный синтаксис для него?
В данном примере возможность, разумеется, существует. Как я заметил выше, достаточно убрать явное указание шаблонного аргумента для `foo`, и все сразу заработает
#include <iostream>
template <typename T> struct A
{
template <typename U> void foo(U u);
};
template <typename T> class B
{
friend void A<T>::foo(T);
int b;
};
template <typename T> template <typename U>
void A<T>::foo(U u)
{
std::cout << "Hello World" << std::endl;
B<int> b;
b.b = 42;
}
int main()
{
A<int>().foo(42); // OK
//A<int>().foo(2.0); // ERROR: нет доступа к `B<int>::b`
//A<double>().foo(42); // ERROR: нет доступа к `B<int>::b`
}
В таком вариант компилятор дедуцирует правильную специализацию шаблона `foo` по типу аргумента. Но что бы мы делали, если бы у `foo` не было явных аргументов?
Явное указание аргумента работает без проблем для отдельностящих шаблонных функций и шаблонных методов обычных (нешаблонных) классов. Проблема возникает именно в ситуации "шаблон в шаблоне". Возможно ли это?
Здравствуйте, Андрей Тарасевич, Вы писали:
АТ>Обратите внимание, что во friend-объявлении я хочу явно указать параметр для шаблона функции. Возможно ли это? (Если я уберу `<T>` после `foo`, то все вопросы отпадут.)
Может так:
template <typename T>
struct A
{
template <typename U>
void foo(U u);
};
template <typename T>
class B
{
template<typename U>
friend void A<T>::foo(U);
};
int main()
{
B<int> b;
}
Здравствуйте, Videoman, Вы писали:
V>Здравствуйте, Андрей Тарасевич, Вы писали:
АТ>>Обратите внимание, что во friend-объявлении я хочу явно указать параметр для шаблона функции. Возможно ли это? (Если я уберу `<T>` после `foo`, то все вопросы отпадут.)
V>Может так:V>template <typename T>
V>struct A
V>{
V> template <typename U>
V> void foo(U u);
V>};
V>template <typename T>
V>class B
V>{
V> template<typename U>
V> friend void A<T>::foo(U);
V>};
V>int main()
V>{
V> B<int> b;
V>}
V>
Так-то, конечно, можно, но это другое: это неограниченная дружба для всяких `foo`. Все специализации `foo` в рамках `A<T>` будут дружить с `B<T>`. То есть, например, `A<int>::foo<double>` будет иметь доступ к внутренностям `B<int>`. А интересует все-таки ограниченная дружба: только `A<int>::foo<int>` должна иметь доступ к `B<int>`.
То есть интересует аналог cинтаксиса
struct A
{
template <typename U>
void foo(U u);
};
template <typename T>
class B
{
friend void A::foo<T>(T);
};
но для ситуации, когда `A` является шаблоном класса.
АТ>Почесав немного репу, говорим себе: "Ага! Это же вложенный шаблон в шаблоне! А не попробовать ли нам добавить тут `template`?".
АТ>Вариант 2
АТ>template <typename T> struct A
{
template <typename U> void foo(U u);
};
template <typename T> class B
{
friend void A<T>::template foo<T>(T);
};
АТ>На этом этапе GCC молча принимает такие объявления, в то время как Clang продолжает жаловаться
АТ>error: 'template' keyword not permitted here
friend void A<T>::template foo<T>(T);
И правильно. Ибо
> 4 The keyword template is said to appear at the top level in a qualified-id if it appears outside of a template-argument-list or decltype-specifier. In a qualified-id of a declarator-id ..., the keyword template shall not appear at the top level.
или
> 5 The keyword template shall not appear immediately after a declarative nested-name-specifier
Здравствуйте, σ, Вы писали:
АТ>>Вариант 1
АТ>>Наивная попытка объявить специализацию шаблонного метода специализации одного шаблонного класса другом другого шаблонного класса:
АТ>>σ>template <typename T> struct A
σ>{
σ> template <typename U> void foo(U u);
σ>};
σ>template <typename T> class B
σ>{
σ> friend void A<T>::foo<T>(T);
σ>};
σ>
σ>Говорят, это ОК.
Я тоже так думал. Но современные версии GCC и Clang отвергают этот код сразу.
Интересным поведением обладает более старая версия Clang (на
http://coliru.stacked-crooked.com/)
Вот такой код компилируется и работает
#include <iostream>
template <typename T> struct A
{
template <typename U> void foo(U u);
};
template <typename T> class B
{
friend void A<T>::foo<T>(T);
int b;
};
template <typename T> template <typename U>
void A<T>::foo(U u)
{
std::cout << "Hello World " << u << std::endl;
}
int main()
{
A<int>().foo(42);
A<int>().foo(2.0);
A<double>().foo(42);
}
http://coliru.stacked-crooked.com/a/6a17f5ecf365363e
Но достаточно добавить внутрь метода инстанцирование `B`, как начинают лезть ошибки линкера
...
template <typename T> template <typename U>
void A<T>::foo(U u)
{
std::cout << "Hello World " << u << std::endl;
B<int> b;
}
int main()
{
A<int>().foo(42);
}
main.cpp:(.text+0x12): undefined reference to `void A<int>::foo<int>(int)'
http://coliru.stacked-crooked.com/a/830787c749b16c9b