将函数声明为朋友时尖括号是什么意思?

时间:2018-10-10 22:47:57

标签: c++ c++11 templates friend-function function-templates

我很难理解函数声明为朋友时的<>表示法。 (这是由于需要定义一个函数的主体,而该函数的主体是注入到外部自由函数中的朋友。)

template<class T> class A;

template<class T> double f(A<T> const& a);

template<class T>
class A{
    double p_;
    friend double f<>(A<T> const& a); // same as friend double f<>(A const& a);
};

这完全等于friend double f<T>(A<T> const& a);吗? 如果是这样,此符号<>的目的是什么?毕竟f没有默认的模板参数。

是这种情况吗?

template<class T1, class T2, ...>
class A{
    double p_;
    friend double f<>(A const& a); // same as double f<T1, T2, ...>?
};

2 个答案:

答案 0 :(得分:4)

它与friend double f<T>(A<T> const&)相同。通常,您可以使用空模板参数来区分函数非模板f和函数模板f。如果您没有<>,则编译器将创建一个完全独立的非模板函数f,而其他f<T>将无法访问私有成员。

template<class T> class A;
template<class T> double f(A<T> const& a);
template<class T>
class A {
  double p_;
  friend double f(A<T> const& a); // notice omission of <>, declared as non-template
};

template<class T>
double f(A<T> const& a) {
  return a.p_;
}

int main() {
  f<>( A<int>{} ); // 'double A<int>::p_' is private within this context
}
  

是更普遍的情况:

template<class T1, class T2, ...>
class A{
    double p_;
    friend double f<>(A const& a); // same as double f<T1, T2, ...>?
};

例如,如果f在类外被声明为template<class...Ts>f(A<Ts...>);,则是的,它们是等效的。

答案 1 :(得分:4)

在大多数情况下,使用模板名称时,编译器需要确定模板参数是什么,从而确定其代表的模板的确切专业性。

听起来您熟悉类模板的完成方式(为简单起见忽略了模板参数包):

  1. <>中的每个参数都是模板参数之一,按从头开始的顺序。

  2. 如果模板中的模板参数多于<>中的参数,则将默认模板参数用于其余位置(并且这些默认模板参数必须存在)。

但是对于函数模板,还有第三种可能性,即自动推导模板参数。您可能熟悉以func(a1, a2)之类的形式从对函数模板的调用中推断出模板参数的想法。但是推导也可能以func<x1, x2>(a1, a2)之类的形式进行,并且可以与模板参数的其他来源混合并匹配(再次忽略模板参数包):

  1. <>中的每个参数都是模板参数之一,按从头开始的顺序。在继续进行下一步之前,将这些替换为函数类型中各处的相应模板参数。

  2. 可以推断出任何剩余的模板参数(从调用中的参数表达式的类型中推导,或者在friend这样的情况下,与声明匹配时从声明的参数类型中推论得出)。

    < / li>
  3. 对于未明确指定和推论的所有其余模板参数,可以使用默认模板参数。 (仅C ++ 11和更高版本-C ++ 03根本不允许函数模板具有默认模板参数。)

因此,在函数调用的情况下,使用func<>(a1, a2)是有效的,这意味着所有模板参数都应从a1a2的类型推导得出,否则取自默认模板参数。这与func(a1, a2)几乎相同,不同之处在于对于func(a1, a2),重载解析可能会选择一个非模板函数,该函数也称为func;但是使用func<>(a1, a2),只有模板才有资格考虑。

类似地,在朋友声明中可能需要模板自变量列表,以确保编译器知道其命名为函数模板的特化,而不是普通的非模板函数。同样,假设可以从函数参数类型推导所有模板参数,通常会使用一个空列表<>。请注意此处的差异:

template <class T> class A;

template <class X> void f1(A<X>);
template <class X> void f2(A<X>);
template <class X> void f3(A<X>);
template <class X> void f4(A<X>);

template <class T>
class A {
    // For each class type A<T>, declares just the one specialization f1<T>
    // to be a friend.  So f1<int>(A<int>) is a friend of A<int>, but is not
    // a friend of A<double>.
    friend void f1<T>(A<T>);

    // Exactly the same (but for f2<T>).
    // A is the "injected class name" typedef for A<T>.
    // The argument for f2's X is deduced to be X=T.
    friend void f2<>(A);

    // Declares ALL specializations of f3 to be friends of all specializations of A.
    template <class U>
    friend void f3(A<U>);

    // Declares a non-template function.  Each class type A<T> declares
    // a different function unrelated to the template f4 above or to
    // the f4 declared by other A<U> types.  You could define the
    // individual overloaded functions void f4(A<int>), void f4(A<double>),
    // etc., but only one at a time, and only if you know all the
    // possible types to be used!
    friend void f4(A);
};
  

是这种情况吗?

template<class T1, class T2, ...>
class A{
    double p_;
    friend double f<>(A const& a); // same as double f<T1, T2, ...>?
};
     

可能。虽然这不只是直接从要实例化的封闭类模板专门化中获取模板参数的问题。模板参数推论可能会更出色。例如,如果我们有

template <class T1, class T2> class A;

template <class X>
double f(A<X, X> const& a);   // #1
template <class X>
double f(A<X, X*> const& a);  // #2
template <class X, class Y>
double f(A<X, Y> const& a);   // #3

template <class T1, class T2>
class A {
    friend double f<>(A const&);
};

然后,A的不同专业化所匹配的模板声明实际上将有所不同!模板#1的功能double f<int>(A<int, int> const&)A<int, int>的朋友,模板#2的功能double f<int>(A<int, int*> const&)A<int, int*>的朋友,功能double f<int*, int>(A<int*, int> const&)来自模板#3的是A<int*, int>的朋友。没有其他模板专业是A的这三个专业的朋友。