Как явно создать экземпляр класса шаблона, который имеет вложенный класс с функцией 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.