解决模板的好友功能时遇到的问题

时间:2018-10-12 11:06:07

标签: c++ templates friend

我很难使它正常工作。这是我的问题的MVCE,已经通过了编译阶段

template<typename T>
struct foo
{
    using type = T;
    friend type bar(foo const& x) { return x.X; }
    foo(type x) : X(x) {}
  private:
    type X;
};

template<typename> struct fun;
template<typename T> fun<T> bar(foo<T> const&, T);  // forward declaration

template<typename T>
struct fun
{
    using type = T;
    friend fun bar(foo<type> const& x, type y)
    { return {bar(x)+y}; }
  private:
    fun(type x) : X(x) {}
    type X;
};

int main()
{
    foo<int> x{42};
    fun<int> y = bar(x,7);    // called here
};

编译器需要前向声明才能解析main()中的调用(原因请参见this answer)。但是,编译器现在在链接/加载阶段抱怨:

  

体系结构x86_64的未定义符号:“有趣   bar(foo const&,int)“,引用自:         _main在foo-00bf19.o ld中:找不到针对架构x86_64的符号

即使该函数在friend声明中定义。如果相反,我将定义移到struct func<>之外,即

template<typename T>
struct fun
{
    using type = T;
    friend fun bar(foo<type> const& x, type y);
  private:
    fun(type x) : X(x) {}
    type X;
};

template<typename T>
inline fun<T> bar(foo<T> const& x, T y)
{ return {bar(x)+y}; }

编译失败,

foo.cc:29:10: error: calling a private constructor of class 'fun<int>'
{ return {bar(x)+y}; }

那么,我该如何工作呢? (编译器:Apple LLVM版本9.0.0(clang-900.0.39.2),c ++ 11)

4 个答案:

答案 0 :(得分:2)

好玩的朋友声明必须与函数模板的正向声明匹配,否则会生成不相关的函数:

template<typename TT> fun<TT> friend ::bar(foo<TT> const &, TT);

定义应放在外部:

template<typename T> fun<T> bar(foo<T> const& x, T y)
{ return {bar(x)+y}; }

online compiler

演示该问题的较短代码是:

void foo(void);

template<typename T>
struct bar
{
    friend void foo(void) {}
};

int main()
{
    foo(); // undefined reference to `foo()'
    return 0;
}

绝不会使用类内函数定义:

  

17.8.1隐式实例化[temp.inst]

     
      
  1. 类模板特殊化的隐式实例化导致声明的隐式实例化,但不隐含类成员函数,成员类,作用域成员枚举,静态数据成员,成员的定义,默认参数或noexcept-specifiers模板和friends;
  2.   

答案 1 :(得分:2)

关于朋友功能有一些古怪的规则。

namespace X {
  template<class T>
  struct A{
    friend void foo(A<T>) {}
  };
}
上面的

foo不是模板函数。它是一个非模板的朋友功能,存在于A内的命名空间中,但只能通过ADL查找来找到;它不能直接命名为X::foo

与模板成员很可能本身就是模板一样,这是一个非模板的朋友函数,是为A的每个模板类实例创建的。

namespace X{
  template<class T>
  void foo(A<T>);
}

foo是命名空间foo中名为X的功能模板。它与上面的非模板朋友功能foo 不相同

由此,您的大多数错误都显而易见。您认为的前向声明是一个不相关的模板函数。您以为是朋友,不是,所以您无权访问私有构造函数。

我们可以通过几种方法解决此问题。我最喜欢的方法是添加标签类型。

template<class T>struct tag_t{using type=T;};
template<class T>
constexpr tag_t<T> tag{};

现在我们可以使用tag进行ADL调度:

template<typename T>
struct fun {
  using type = T;
  friend fun bar(tag_t<fun>, foo<type> const& x, type y)
  { return {bar(x)+y}; }
private:
  fun(type x) : X(x) {}
  type X;
};

然后在主要方面起作用:

foo<int> x{42};
fun<int> y = bar(tag<fun<int>>, x, 7);    // called here

但是您可能不想提及tag<fun<int>>,所以我们只创建一个非朋友bar来替我们打电话:

template<class T>
fun<T> bar(foo<T> const& x, type y)
{ return bar( tag<T>, x, y ); }

现在ADL发挥了魔力,并且找到了正确的非模板bar

另一种方法涉及使template函数bar成为fun的朋友。

答案 2 :(得分:1)

好的,我找到了答案:

为使好友声明正常工作,必须将其声明为函数 template ,即

template<typename T>
struct fun
{
    using type = T;
    friend fun bar<T>(foo<type> const& x, type y);
    //            ^^^
  private:
    fun(type x) : X(x) {}
    type X;
};

template<typename T>
inline fun<T> bar(foo<T> const& x, T y)
{ return {bar(x)+y}; }

但是,将定义与好友声明结合在一起仍然失败:

template<typename T>
struct fun
{
    using type = T;
    friend fun bar<T>(foo<type> const& x, type y)
    { return {bar(x)+y}; }
  private:
    fun(type x) : X(x) {}
    type X;
};

导致:

foo.cc:20:16: warning: inline function 'bar<int>' is not defined [-Wundefined-inline]
    friend fun bar<T>(foo<T> const& x, T y)
               ^
1 warning generated.
Undefined symbols for architecture x86_64:
  "fun<int> bar<int>(foo<int> const&, int)", referenced from:
      _main in foo-c4f1dd.o
ld: symbol(s) not found for architecture x86_64

我还不太了解,因为前向声明仍然存在。

答案 3 :(得分:1)

这个为我编译:

template<typename T>
struct foo
{
    using type = T;
    friend type bar(foo const& x) { return x.X; }
    foo(type x) : X(x) {}
  private:
    type X;
};

template<typename> struct fun;
template<typename T> fun<T> bar(foo<T> const&, T);  // forward declaration

template<typename T>
struct fun
{
    using type = T;
    friend fun bar<type>(foo<type> const& x, type y);
  private:
    fun(type x) : X(x) {}
    type X;
};

template<typename T>
inline fun<T> bar(foo<T> const& x, T y)
{ return {bar(x)+y}; }

int main()
{
    foo<int> x{42};
    fun<int> y = bar(x,7);    // called here
};

使用GCC 8.2.1。我在好友声明中添加了模板名称。