5

C++ 常见问题解答 35.16

http://www.parashift.com/c++-faq-lite/template-friends.html

#include <iostream>

template<typename T>
class Foo {
public:
  Foo(T const& 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_;
};

作者声称:

'当编译器在类定义中正确地看到友元行时,就会出现障碍。那时它还不知道友元函数本身就是模板(为什么?类模板成员函数不是默认的函数模板吗?);它假设它们是这样的非模板:'

Foo<int> operator+ (const Foo<int>& lhs, const Foo<int>& rhs)
{ ... }  

std::ostream& operator<< (std::ostream& o, const Foo<int>& x)
{ ... }

为什么上述非模板?这些模板不是通过 int 实例化的吗?

'当你调用 operator+ 或 operator<< 函数时,这种假设会导致编译器生成对非模板函数的调用,但链接器会给你一个“未定义的外部”错误,因为你从未真正定义过那些非模板函数. '

事实上,要让编译器将上面的内容识别为函数模板,程序员必须明确地这样做,如下所示:

template<typename T> class Foo;  // pre-declare the template class itself
template<typename T> Foo<T> operator+ (const Foo<T>& lhs, const Foo<T>& rhs);
template<typename T> std::ostream& operator<< (std::ostream& o, const Foo<T>& x);

谁能解释一下?我觉得这很烦人,不知道为什么编译器不只是通过将 T 替换为“int”来实例化 Foo 类的实例,然后就结束了。

谢谢。

4

1 回答 1

5

类模板成员函数是模板的一部分,因此用模板实例化,但朋友不是。考虑非模板情况:

struct S {
    friend void foo(S);
};

注意void foo(S)此时不必声明;friend声明是说,如果void foo(S)定义了一个函数,那么该函数将有权访问S. 它可能永远不会被真正定义,这很好。

使用模板,情况是一样的:

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

这就是说,对于任何类型T如果void foo(S<T>)定义了一个函数,那么该函数就可以访问S<T>. 通过重载,该函数应该是一个具体的函数:

void foo(S<char>) { }
void foo(S<int>) { }

编译器不知道您计划稍后提供可用于所有T. 相反,如果已经声明了适当的函数模板,那么如果您通过添加尖括号指定它应该被实例化,它将被实例化。

至于为什么必须前向声明模板,没有理由“模板”必须只有一个声明。考虑:

#include <iostream>
template<typename T> struct S;
template<typename T> void foo(S<T>);
template<typename T> void foo(S<T *>);
template<typename T> struct S {
    friend void foo<>(S);
};
template<typename T> void foo(S<T>) { std::cout << "template template friend\n"; }
template<typename T> void foo(S<T *>) { std::cout << "template specialization template friend\n"; }
template void foo(S<void *>);
int main() {
    foo(S<int>());
    foo(S<void *>());
}

这里有 的两个特化foo,它们都必须前向声明,以便friend可以在它们之间进行选择。

于 2012-08-08T12:26:33.520 回答