首页 文章

模板类的模板友好功能

提问于
浏览
9

我正在努力解决this question中描述的问题(将模板函数声明为模板类的朋友),我相信第二个答案就是我想做的事情(转发声明模板函数,然后将专业化命名为朋友) . 我有一个问题是,一个稍微不同的解决方案是否实际上是正确的,或者只是恰好在Visual C 2008中工作 .

测试代码是:

#include <iostream>

// forward declarations
template <typename T>
class test;

template <typename T>
std::ostream& operator<<(std::ostream &out, const test<T> &t);

template <typename T>
class test {
  friend std::ostream& operator<< <T>(std::ostream &out, const test<T> &t);
  // alternative friend declaration
  // template <typename U>
  // friend std::ostream& operator<<(std::ostream &out, const test<T> &t);

  // rest of class
  };

template <typename T>
std::ostream& operator<<(std::ostream &out, const test<T> &t) {
  // output function defined here
  }

首先,我发现一个奇怪的事情是,如果我更改 operator<< 的前向声明以使其不匹配(例如, std::ostream& operator<<(std::ostream &out, int fake); ,所有内容仍然编译并且正常工作(要清楚,我不要't need to define such a function, only declare it). However, as in the linked-to question, removing the forward declaration causes a problem as the compiler seems to think I' m声明数据成员而不是一个朋友的功能 . 我很确定这个行为是一个Visual C 2008错误 .

有趣的是当我删除前向声明并在上面的代码中使用替代友元声明时 . 请注意,模板参数 U 在我的参考书中没有找到一个好的答案 .

请注意,虽然朋友声明 template <typename U> friend ... const test<U> &t); 也有效,但这实际上为运营商 friend 的每个实例提供了对 test 的任何实例的访问权限,而我想要的是 test<T> 的私有成员只能从 operator<< <T> 访问 . 我通过在 operator<< 中实例化 test<int> 并访问私有成员来测试这个;当我尝试输出 test<double> 时,这会导致编译错误 .

概要:删除前向声明并切换到上面代码中的替代友元声明似乎产生相同的结果(在Visual C 2008中) - 这段代码实际上是否正确?

更新:对代码的任何上述修改都不能在gcc下工作,所以我猜这些是Visual C编译器中的错误或“功能” . 我仍然很欣赏熟悉该标准的人的见解 .

1 回答

  • 7

    ...if I change the forward declaration of operator<< so that it doesn't match

    朋友功能应被视为一种非常特殊的声明 . 本质上,编译器足以解析声明,但是除非您实际专门化类,否则不会进行语义检查 .

    在进行建议的修改之后,如果您再实例化 test ,则会收到有关声明不匹配的错误:

    template class test<int>;
    

    ...However ... removing the forward declaration causes a problem

    编译器尝试解析声明以存储它,直到类模板专门化 . 在解析期间,编译器到达声明中的 <

    friend std::ostream& operator<<  <
    

    operator<< 可以跟随 < 的唯一方法是它是否是模板,因此会进行查找以检查它是否为模板 . 如果找到函数模板,则 < 被认为是模板参数的开始 .

    删除前向声明时,未找到模板, operator<< 被视为对象 . (这也是为什么当你添加 using namespace std 代码继续编译时,因为必须有 operator<< 的模板声明) .

    ...when I remove the forward declarations and use the alternative friend declaration in the code above. Note that the template parameter U doesn't appear in the following signature...

    不要求在函数模板的参数中使用所有模板参数 . 替代声明用于新的函数模板,只有在命名空间中声明并指定显式模板参数时才能调用 .

    一个简单的例子是:

    class A {};
    template <typename T> A & operator<<(A &, int);
    
    void foo () {
      A a;
      operator<< <int> (a, 10);
    }
    

    ...is this code actually correct?..

    那么这有两个部分 . 第一个是替代的友元函数在后面的范围内没有引用声明:

    template <typename T>
    class test {
      template <typename U> 
      friend std::ostream& operator<<(std::ostream &out, const test<T> &t);
      };
    
    template <typename T> 
    std::ostream& operator<<(std::ostream &out, const test<T> &t);  // NOT FRIEND!
    

    friend函数实际上将在每个特化的命名空间中声明:

    template <typename U> 
    std::ostream& operator<<(std::ostream &out, const test<int> &t);
    template <typename U> 
    std::ostream& operator<<(std::ostream &out, const test<char> &t);
    template <typename U>
    std::ostream& operator<<(std::ostream &out, const test<float> &t);
    

    operator<< <U> 的每个特化都可以根据其参数 test<T> 的类型访问特定的特化 . 因此,实质上访问受限于您的要求 . 但是正如我之前提到的,这些函数基本上不能用作运算符,因为你必须使用函数调用语法:

    int main ()
    {
      test<int> t;
      operator<< <int> (std << cout, t);
      operator<< <float> (std << cout, t);
      operator<< <char> (std << cout, t);
    }
    

    根据前一个问题的答案,您可以使用litb建议的前向声明,或者根据Dr_Asik's answer(可能就是我要做的)来定义友元函数内联 .

    UPDATE: 2nd Comment

    ...changing the forward declaration before the class; the one in the class still matches the function that I implement later...

    正如我在上面指出的那样,编译器在声明中看到 < 时检查 operator<< 是否是模板:

    friend std::ostream& operator<<  <
    

    它通过查找名称并检查它是否是模板来完成此操作 . 只要你有一个伪前向声明然后这个"tricks"编译器将你的朋友视为模板名称,所以 < 被认为是模板参数列表的开头 .

    稍后,当您实例化该类时,您确实有一个匹配的有效模板 . 从本质上讲,您只是欺骗编译器将朋友视为模板专业化 .

    你可以在这里这样做,因为(正如我之前所说),此时不会进行语义检查 .

相关问题