【问题标题】:How to avoid redefinition error in case of in-class definition of friend function template?如何避免友元函数模板类内定义的重定义错误?
【发布时间】:2012-03-08 16:15:10
【问题描述】:

考虑这段代码:

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&lt;T&gt; 成为Base&lt;T&gt; 的朋友,但不是f&lt;U&gt; 成为Base&lt;T&gt; 的朋友,如果UT 不一样。同时,我也想提供类内部的定义。有可能吗?

【问题讨论】:

  • 我看不出编译器为什么会出错,而且似乎clang人也没有看到原因,因为它是用clang编译的。
  • 如果我取消注释 B 的定义,GCC 和 MSVC10 都会出错。
  • @PlasmaHH:因为友元函数不是成员函数,因此可以不依赖于类模板的参数。

标签: c++ templates friend function-templates class-template


【解决方案1】:

你真的需要在类中定义f 吗?如果你在外面定义它,你的问题就消失了,你也可以强制执行你想要的一对一关系(即只有 f&lt;T&gt;Base&lt;T&gt; 的朋友):

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&lt;T&gt;Base&lt;T&gt; 的朋友这一事实不会阻止以下代码编译:

B b;
f<A>(&b); // compiles, f<A> calls Base<A>::run, but the cast is wrong

【讨论】:

  • 我只能看到一个有效的理由来肯定地回答“你真的需要在类中定义f 吗?”:需要排除对f 参数类型的隐式转换。当友元函数在类中定义时,它只能通过 ADL 找到,否则可以通过正常查找找到,这可能(或可能不是)不受欢迎。请参阅there 了解更多信息。
  • 请注意 f 必须定义为 inline 以使此代码等效于类内定义,隐式为 inline (C++11 §11.3/7, C ++17 §14.3/7)。在inline 模板之间有一个difference 而不是。
【解决方案2】:

友元函数是一个全局函数,即使你将它的实现放到任何类的主体中。问题是当你实例化Base&lt;T&gt; 两次(在任何上下文中)时,你提供了f 的两个实现。注意,f依赖于T,它不能使用T;对于所有Base&lt;T&gt;,它的功能相同。

一个简单的解决方案是只在类模板内提供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++编译

【讨论】:

  • 我知道它有效(我已经尝试过了),我在帖子中说过我不想这样做。
  • 根据您提供的限制,我认为您必须这样做,原因我在第一段中解释过。
猜你喜欢
  • 1970-01-01
  • 2016-10-19
  • 2011-05-16
  • 2016-06-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-22
相关资源
最近更新 更多