Информация об изменениях

Сообщение Шаблонные друзья: шаблонный метод шаблонного класса от 18.11.2022 2:39

Изменено 18.11.2022 2:40 Андрей Тарасевич

Шаблонные друзья: шаблонный метод шаблонного класса
Вариант 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
  //A<double>().foo(42);  // ERROR
}


В таком вариант компилятор дедуцирует правильную специализацию шаблона `foo` по типу аргумента. Но что бы мы делали, если бы у `foo` не было явных аргументов?

Явное указание аргумента работает без проблем для отдельностящих шаблонных функций и шаблонных методов обычных (нешаблонных) классов. Проблема возникает именно в ситуации "шаблон в шаблоне". Возможно ли это?
Шаблонные друзья: шаблонный метод шаблонного класса
Вариант 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
  //A<double>().foo(42);  // ERROR
}


В таком вариант компилятор дедуцирует правильную специализацию шаблона `foo` по типу аргумента. Но что бы мы делали, если бы у `foo` не было явных аргументов?

Явное указание аргумента работает без проблем для отдельностящих шаблонных функций и шаблонных методов обычных (нешаблонных) классов. Проблема возникает именно в ситуации "шаблон в шаблоне". Возможно ли это?