解决模板友元功能的问题

problems with resolving friend function of template

我在让它工作时遇到了一些问题。这是我通过编译阶段的问题的 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)。但是,编译器现在在 linking/loading 阶段抱怨:

Undefined symbols for architecture x86_64: "fun bar(foo const&, int)", referenced from: _main in foo-00bf19.o ld: symbol(s) not found for architecture x86_64

即使函数是在友元声明中定义的。相反,我将定义移到 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)

好的,我找到答案了:

为了使好友声明正常工作,它必须被限定为函数模板,即

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

我不太明白,因为前面的声明仍然存在。

这个为我编译:

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。我添加的是朋友声明的模板指定。

fun 内部的友元声明必须匹配函数模板的前向声明,否则会产生一个不相关的函数:

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;
}

In-class 永远不会使用函数定义:

17.8.1 Implicit instantiation [temp.inst]

  1. The implicit instantiation of a class template specialization causes the implicit instantiation of the declarations, but not of the definitions, default arguments, or noexcept-specifiers of the class member functions, member classes, scoped member enumerations, static data members, member templates, and friends;

友元函数有一些古怪的规则。

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

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

很像模板的成员本身可以是模板,也可以不是,这是为 A 的每个模板 class 实例化创建的非模板友元函数。

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

这个 foo 是命名空间 X 中名为 foo 的函数模板。它与上面的非模板友元函数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 的好友。