Шаблонные друзья: шаблонный метод шаблонного класса
От: Андрей Тарасевич Беларусь  
Дата: 18.11.22 02:39
Оценка:
Вариант 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` не было явных аргументов?

Явное указание аргумента работает без проблем для отдельностящих шаблонных функций и шаблонных методов обычных (нешаблонных) классов. Проблема возникает именно в ситуации "шаблон в шаблоне". Возможно ли это?
Best regards,
Андрей Тарасевич
Отредактировано 18.11.2022 3:43 Андрей Тарасевич . Предыдущая версия . Еще …
Отредактировано 18.11.2022 2:40 Андрей Тарасевич . Предыдущая версия .
Re: Шаблонные друзья: шаблонный метод шаблонного класса
От: Videoman Россия https://hts.tv/
Дата: 18.11.22 08:59
Оценка:
Здравствуйте, Андрей Тарасевич, Вы писали:

АТ>Обратите внимание, что во 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; 
}
Re[2]: Шаблонные друзья: шаблонный метод шаблонного класса
От: Андрей Тарасевич Беларусь  
Дата: 18.11.22 19:08
Оценка:
Здравствуйте, 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` является шаблоном класса.
Best regards,
Андрей Тарасевич
Re: Шаблонные друзья: шаблонный метод шаблонного класса
От: σ  
Дата: 18.11.22 19:20
Оценка: 7 (1)
АТ>Вариант 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);
};


Говорят, это ОК.
Re: Шаблонные друзья: шаблонный метод шаблонного класса
От: σ  
Дата: 18.11.22 23:52
Оценка:
АТ>Почесав немного репу, говорим себе: "Ага! Это же вложенный шаблон в шаблоне! А не попробовать ли нам добавить тут `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
Re[2]: Шаблонные друзья: шаблонный метод шаблонного класса
От: Андрей Тарасевич Беларусь  
Дата: 19.11.22 04:48
Оценка:
Здравствуйте, σ, Вы писали:

АТ>>Вариант 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
Best regards,
Андрей Тарасевич
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.