Как явно создать экземпляр класса шаблона, который имеет вложенный класс с функцией friend (С++)
Наверное, раньше задавали вопрос, но все это приближается к пределу моего понимания и понимания С++, поэтому я немного замешкаюсь, понимая, о чем говорят и что именно происходит. Позвольте мне просто перейти прямо к коду. Это работает:
template <typename T>
class Foo
{
struct Bar
{
Bar() {}
~Bar() noexcept {}
Bar(Bar&& b) : Bar() { swap(*this, b); }
friend void swap(Bar& b1, Bar& b2) { /* ... */ }
};
};
template class Foo<int>; // explicit instantiation of Foo with int type
Но как перенести определение swap
вне тела структуры Bar
? Если я это сделаю:
template <typename T>
class Foo {
struct Bar {
// ...
Bar(Bar&& b) : Bar() { swap(*this, b); } // line 16
// ...
template <typename V>
friend void swap(typename Foo<V>::Bar&, typename Foo<V>::Bar&);
};
};
template <typename T>
void swap(typename Foo<T>::Bar& b1, typename Foo<T>::Bar& b2) {} // line 26
template class Foo<int>; // line 31
g++ (4.7.1, flags: -Wall -std = С++ 11) сообщает:
main.cpp: In instantiation of ‘Foo<T>::Bar::Bar(Foo<T>::Bar&&)
[with T = int; Foo<T>::Bar = Foo<int>::Bar]’:
main.cpp:31:16: required from here
main.cpp:16:28: error: no matching function for call to
‘swap(Foo<int>::Bar&, Foo<int>::Bar&)’
main.cpp:16:28: note: candidate is:
main.cpp:26:6: note: template<class T> void swap(typename Foo<T>::Bar&,
typename Foo<T>::Bar&)
main.cpp:26:6: note: template argument deduction/substitution failed:
main.cpp:16:28: note: couldn't deduce template parameter ‘T’
Я предполагаю, что код для swap
также должен быть создан при явном экземпляре Foo
, что имеет смысл, но почему компилятор не может определить, что swap(Foo<int>::Bar&...)
необходимо создать? Почему смена шаблона не выполняется? Или у меня все не так?
ОБНОВЛЕНИЕ 1
С
template <typename T> class Foo;
template <typename T>
void swap(typename Foo<T>::Bar& b1, typename Foo<T>::Bar& b2);
template <typename T>
class Foo {
struct Bar {
Bar(Bar&& b) : Bar() { swap(*this, b); } // line 19
friend void swap<>(Foo<T>::Bar& b1, Foo<T>::Bar& b2); // line 20
};
};
template <typename T>
void swap(typename Foo<T>::Bar& b1, typename Foo<T>::Bar& b2) {} // line 26
template class Foo<int>; // line 29
g++ (4.7.1, flags: -Wall -std = С++ 11) сообщает:
main.cpp: In instantiation of ‘struct Foo<int>::Bar’:
main.cpp:29:16: required from here
main.cpp:20:17: error: template-id ‘swap<>’ for ‘void swap(Foo<int>::Bar&, Foo<int>::Bar&)’ does not match any template declaration
main.cpp: In instantiation of ‘Foo<T>::Bar::Bar(Foo<T>::Bar&&) [with T = int; Foo<T>::Bar = Foo<int>::Bar]’:
main.cpp:29:16: required from here
main.cpp:19:24: error: no matching function for call to ‘Foo<int>::Bar::Bar()’
main.cpp:19:24: note: candidate is:
main.cpp:19:5: note: Foo<T>::Bar::Bar(Foo<T>::Bar&&) [with T = int; Foo<T>::Bar = Foo<int>::Bar]
main.cpp:19:5: note: candidate expects 1 argument, 0 provided
main.cpp:19:28: error: no matching function for call to ‘swap(Foo<int>::Bar&, Foo<int>::Bar&)’
main.cpp:19:28: note: candidate is:
main.cpp:26:8: note: template<class T> void swap(typename Foo<T>::Bar&, typename Foo<T>::Bar&)
main.cpp:26:8: note: template argument deduction/substitution failed:
main.cpp:19:28: note: couldn't deduce template parameter ‘T’
ОБНОВЛЕНИЕ 2
ОК, так что это невозможно. Piotr связал с вывод вложенного класса внутри шаблона, но я не понимаю ответа. Почему не может быть swap
определено вне его объявления? Насколько я (неправильно) понимаю вещи, почему компилятор не может создать код для swap(Foo<int>::Bar&...)
и ссылаться на него в коде для явного создания Foo<int>
? Неужели я совершенно не понял, что происходит? В чем проблема?
ОБНОВЛЕНИЕ 3
ОК, это невозможно сделать, потому что, если существуют специализированные шаблоны, компилятор не может гарантировать, что вызовы swap
, определенные вне Foo
, недвусмысленны, поскольку Foo<some_class>::Bar
может быть чем-то совершенно иным в конкретной специализации. Надеюсь, я прав. Но почему g++ не предупреждает об этом, прежде чем я создам явное создание Foo
?
template <typename T>
class Foo {
struct Bar {
// ...
Bar(Bar&& b) : Bar() { swap(*this, b); }
// ...
template <typename V>
friend void swap(typename Foo<V>::Bar&, typename Foo<V>::Bar&);
};
};
template <typename T>
void swap(typename Foo<T>::Bar& b1, typename Foo<T>::Bar& b2) {}
//template class Foo<int>; // let comment this explicit instantiation out.
Этот код компилирует fine (g++ 4.7.1, flags: -Wall -std = С++ 11). Но не следует ли мне предупреждать меня, что этот код может вызвать проблемы? Когда я добавляю явное создание Foo
, проблема заключается не в самой строке, а в коде swap
, реализованном вне Foo
.
Ответы
Ответ 1
Проблема не в friend
.
Проблема заключается в самой этой функции:
template <typename T>
void swap(typename Foo<T>::Bar& b1, typename Foo<T>::Bar& b2) {} // line 26
Отменить шаблонный параметр T
из вложенного класса невозможно, см. Вывести вложенный шаблон внутри шаблона или даже лучше ответить на этот вопрос: fooobar.com/questions/343813/...
Чтобы привести пример, почему это невозможно, рассмотрите эту функцию:
template <class T>
void foo(typename A<T>::Bar);
И это определение:
template <class T>
struct A { typedef int Bar; };
И этот вызов:
int a;
foo(a);
Что такое T
в этом примере? Это int
, потому что A<int>::Bar
есть int
, OR float
, потому что
A<float>::Bar
тоже int
ИЛИ что вы хотите.... Вопрос в том, какую функцию вы вызываете foo<int>(int)
или foo<float>(int)
или...
Или привести пример ближе к вопросу:
template <class T>
struct Foo {
struct Bar {};
};
Кажется, что компилятор не должен иметь никаких проблем с этим:
template <class T>
void resolve(typename Foo<T>::Bar*);
Но компилятор даже здесь имеет проблемы, потому что не уверен, что специализация какого-либо другого класса не будет использовать внутреннюю структуру другого класса, например:
template <class T>
struct Foo<T*> {
typedef Foo<T>::Bar Bar;
};
Итак, для:
Foo<void>::Bar b;
resolve(&b);
У компилятора нет возможности узнать, какую версию вызывать:
resolve<void>(Foo<void>::Bar*);
// or
resolve<void*>(Foo<void>::Bar*);
// ^
Что я могу вам посоветовать - использовать встроенный друг - но реализовать его с помощью другого класса шаблонов. Это работает, но я уверен, что это немного переработано:
template <class S>
class ImplementSwap;
template <typename T>
class Foo {
public:
struct Bar {
int a;
Bar() {}
~Bar() {}
friend class ImplementSwap<Foo<T>>;
friend void swap(Foo<T>::Bar& b1, Foo<T>::Bar& b2)
{ ImplementSwap<Foo<T>>::doSwap(b1, b2); }
Bar(Bar&& b) { swap(*this, b); }
};
};
template <class T>
class ImplementSwap<Foo<T>> {
public:
static void doSwap(typename Foo<T>::Bar&,typename Foo<T>::Bar&);
};
template <class T>
void ImplementSwap<Foo<T>>::doSwap(typename Foo<T>::Bar&,typename Foo<T>::Bar&)
{
// this one is not inline....
}
Я сделал Bar public для этого теста:
Foo<int>::Bar a = Foo<int>::Bar(); // move constructor
int main() {
swap(a,a); // explicit swap
}
[СТАР]
Мой предыдущий ответ был совершенно неправильным, и первые комментарии относятся к нему.
friend void swap<>(typename Foo<T>::Bar&, typename Foo<T>::Bar&);
// ^^
[/OLD]
Забастовкa >
Ответ 2
Для меня это должно выглядеть так:
template <typename T>
class Foo
{
struct Bar
{
template <typename V>
friend void swap(typename Foo<V>::Bar&, typename Foo<V>::Bar&);
};
};
template <typename T>
void swap(typename Foo<T>::Bar& b1, typename Foo<T>::Bar& b2) { }
template class Foo<int>;
int main()
{
Foo<int>::Bar bb1, bb2;
swap<int>(bb1, bb2);
}
Это выполняется с помощью gcc: http://ideone.com/zZMYn
Несколько заметок:
-
Объявление друга внутри класса создает прямое объявление объекта, объявленного как друга вне класса. Это относится к классам друзей, функциям, шаблонам. Это означает, что переадресация объявления вашего swap
не требуется.
-
Синтаксис some-name<..>
используется в специализированных шаблонах и экземплярах шаблонов. Он не используется в форвардных декларациях (включая объявления друзей) и определения. В вашем примере у вас есть одно форвардное объявление, одно определение и один вызов (это экземпляр для шаблонов функций).
Если вы вызываете функцию swap как swap(bb1, bb2);
, компилятор не может ее найти. Для меня необходимость вызова этой функции, как в примере выше swap<int>
, скорее является проблемой компилятора, чем требованием языка. Компилятор должен вывести параметры шаблона для вызовов функций шаблона.
ИЗМЕНИТЬ
В приведенном выше примере vars bb1
и bb2
имеют тип Foo<int>::Bar
. Это означает, что создание swap
должно быть:
void swap(Foo<int>::Bar &b1, Foo<int>::Bar &b2) { }
Никакой другой экземпляр не может работать здесь, потому что Foo<float>::Bar
- это другой тип от Foo<int>::Bar
. Невозможно преобразовать Foo<int>::Bar
в Foo<float>::Bar
. Событие, если шаблон для Foo<float>::Bar
будет вызван, не может быть использован. Типы параметров отличаются.
Если для этого шаблона будет несколько специализаций, ситуация может быть более сложной. Но в момент вызова нет ничего, кроме самого шаблона. Для рассмотрения специализация должна быть видна в точке вызова.
Отличный компилятор может справиться с этим случаем. Поскольку работа с спецификацией типа explictit доступна и, похоже, работает, я бы сказал, что текущее состояние gcc полностью Ok.