Как избежать ошибки переопределения в случае определения в классе шаблона функции-друга? - PullRequest
1 голос
/ 17 февраля 2012

Рассмотрим этот код:

template<typename T>
class Base
{
   template<typename U>
   friend void f(void *ptr) {
     static_cast<Base<U>*>(ptr)->run();
   }
   protected:
       virtual void run() = 0; 
};

class A : public Base<A>
{
   protected:
       virtual void run() {}
};

/*
class B : public Base<B>
{
   protected:
       virtual void run() {}
};
*/

Теперь он прекрасно компилируется ( ideone ).Но если я раскомментирую определение B, то оно выдаст следующую ошибку ( ideone ):

prog.cpp: In instantiation of ‘Base<B>’:
prog.cpp:20:   instantiated from here
prog.cpp:6: error: redefinition of ‘template<class U> void f(void*)’
prog.cpp:6: error: ‘template<class U> void f(void*)’ previously defined here

Я знаю ( хорошо, я думаю, что знаю ) причина, по которой он выдает эту ошибку.

Итак, мой вопрос:

Как избежать ошибки переопределения в случае определения в классе шаблона функции-друга?

Пока я предоставляю определение основного шаблона (не специализация) внутри класса, я получу эту ошибку.Существует также еще одна проблема с определением основного шаблона таким образом: он создает все экземпляры f шаблона функции friend из всех экземпляров Base шаблона класса, которых я также хотел бы избежать.Я хочу сделать f<T> другом Base<T>, но не f<U> другом Base<T>, если U и T не совпадают.В то же время я также хочу дать определение внутри класса.Возможно ли это?

Ответы [ 2 ]

4 голосов
/ 17 февраля 2012

Вам действительно нужно определить f в классе? Если вы определите ее снаружи, ваша проблема исчезнет, ​​и вы также сможете принудительно установить однозначное отношение (т. Е. Только f<T> является другом Base<T>):

template <typename T> class Base;

template <typename U>
void f(void *ptr) {
   static_cast<Base<U>*>(ptr)->run();
}

template<typename T>
class Base
{
   friend void f<T>(void *ptr); //only one instanciation is a friend

   protected:
     virtual void run() = 0; 
};

Однако учтите, что тот факт, что только f<T> является другом Base<T>, не помешает компиляции следующего кода:

B b;
f<A>(&b); // compiles, f<A> calls Base<A>::run, but the cast is wrong
1 голос
/ 17 февраля 2012

Дружественная функция - это глобальная функция, даже если вы поместите ее реализацию в тело любого класса. Проблема в том, что когда вы создаете экземпляр Base<T> дважды (в любом контексте), вы предоставляете две реализации f. Обратите внимание, что f не зависит от T и не может использовать T; это одинаковая функция для всех Base<T>.

Простое решение - предоставить только объявление f в шаблоне класса и реализацию вне его:

template<typename T>
class Base
{
  template<typename U>
  friend void f(void *ptr);
  protected:
    virtual void run() = 0;
};


template<typename U>
void f(void *ptr) {
  static_cast<Base<U>*>(ptr)->run();
}

class A : public Base<A>
{
 protected:
   virtual void run() {}
};

class B : public Base<B>
{
protected:
  virtual void run() {}
};

int main() {
}

Приведенный выше код компилируется с моим g ++

...