为什么在没有模板声明编译的情况下包含在模板 class 中使用模板参数的友元函数的实现?

Why is including implementation for a friend function that uses template parameters inside of a template class without a template declaration compile?

给定以下代码。

#include <iostream>
template<typename T>
class Foo 
{
public:
    Foo(const T& value = T());

    friend Foo<T> operator+ (const Foo<T>& lhs, const Foo<T>& rhs)
    {
    // ...
    }
    friend std::ostream& operator<< (std::ostream& o, const Foo<T>& x)
    {
    // ...
    }
private:
    T value_;
};

编译器可以在没有以下语法的情况下编译两个具有模板参数的友元函数没有问题

template <typename T>
friend Foo<T> operator+ (const Foo<T>& lhs, const Foo<T>& rhs)

friend Foo<T> operator+ <>(const Foo<T>& lhs, const Foo<T>& rhs)

friend Foo<T> operator+ <T>(const Foo<T>& lhs, const Foo<T>& rhs)

因为它们已由模板 class 本身的实现定义。

编译器如何在不包含模板声明的情况下使用模板参数编译这些友元函数?为什么只在 class 中实现它们就足够了?

我从 "Why do I get linker errors when I use template friends?"

部分的 here 了解到这个概念

这两个选项,有和没有 template<class T>,做的事情略有不同。

当您以这种方式引入 friend 函数时,您将以一种只能通过 ADL(参数相关查找)访问的方式在封闭的命名空间中引入它。

template<class T>介绍的是功能模板,没有的介绍的是实际功能。

所以这个:

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

表示当foo<int>存在时,创建一个函数bar(foo<int>)。然后 foo<double> 创建 bar(foo<double>).

这些 bar 中的每一个都不是 template 函数。它们是 eaxh 一个具有固定签名的函数,一个新的重载,类似于你写的

void bar(foo<char>){}

紧接着 foo。例外是 friend bar 只能通过 ADL 找到,这改变了冲突和重载解决的工作方式。

现在这个:

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

foo 的每个实例创建一个 template bar。这些并不冲突,因为它们只能通过 ADL 找到。唯一可以找到的是 TX 匹配的那个(在这种情况下——如果有更多参数,它可能会有所不同)。

根据我的经验,template 版本很少是个好主意。