模板类的模板友元函数

发布于 2024-08-12 04:59:13 字数 1965 浏览 4 评论 0 原文

我正在努力解决这个问题中描述的问题(将模板函数声明为模板类的朋友),我相信第二个答案就是我想要做的(转发声明模板函数,然后将专业化命名为朋友)。我有一个问题,一个稍微不同的解决方案是否实际上是正确的,或者只是碰巧在 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);,一切仍然可以编译并工作正确(要明确的是,我不需要定义这样的函数,只需声明它)但是,正如在链接到的问题中一样,删除前向声明会导致问题,因为编译器似乎认为我正在声明一个我很确定这种行为是 Visual C++ 2008 的错误,

有趣的是当我删除上面的代码中的前向声明并使用替代的友元声明时,请注意模板参数 <。 code>U 不会出现在以下签名中。此方法也可以正常编译和工作(无需更改任何其他内容)。我的问题是这是否符合标准或 Visual C++ 2008 的特性(我在参考书中找不到好的答案)。

请注意,虽然友元声明 template 朋友... const test &t); 也有效,这实际上使运算符 friend 的每个实例都可以访问 test 的任何实例,而我想要的是私有的test 的成员只能从 operator<< 访问。我通过在 operator<< 内实例化 test 并访问私有成员来测试这一点;当我尝试输出 test 时,这应该会导致编译错误。

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

更新:以上对代码的任何修改在 gcc 下都不起作用,所以我猜测这些是 Visual C++ 编译器中的错误或“功能”。尽管如此,我仍然希望熟悉该标准的人提供见解。

I was struggling with the issue described in this question (declaring a template function as a friend of a template class), and I believe the 2nd answer is what I want to do (forward declare the template function, then name a specialization as a friend). I have a question about whether a slightly different solution is actually correct or just happens to work in Visual C++ 2008.

Test code is:

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

First, one strange thing I found was that if I change the forward declaration of operator<< so that it doesn't match (for example, std::ostream& operator<<(std::ostream &out, int fake);, everything still compiles and works correctly (to be clear, I don'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 declaring a data member instead of a friend function. I'm pretty sure that this behaviour is a Visual C++ 2008 bug.

The interesting thing is 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. This method also compiles and works correctly (without changing anything else). My question is whether this is conforming to the standard or an idiosyncrasy of Visual C++ 2008 (I couldn't find a good answer in my reference books).

Note that while a friend declaration template <typename U> friend ... const test<U> &t); also works, this actually gives each instance of the operator friend access to any instance of test, while what I want is that the private members of test<T> should only be accessible from operator<< <T>. I tested this by instantiating a test<int> inside the operator<< and accessing a private member; this should cause a compile error when I try to output a test<double>.

Synopsis: Removing the forward declarations and switching to the alternative friend declaration in the code above seems to produce the same result (in Visual C++ 2008) -- is this code actually correct?

UPDATE: Any of the above modifications to the code don't work under gcc, so I'm guessing that these are errors or "features" in the Visual C++ compiler. Still I'd appreciate insights from people familiar with the standard.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

空城之時有危險 2024-08-19 04:59:13

...如果我更改运算符的前向声明<<因此它不匹配

友元函数应该被视为一种非常特殊的声明类型。本质上,编译器会做足够的事情来解析声明,但是除非您实际上专门化了该类,否则不会进行语义检查。

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

template class test<int>;

...但是...删除前向声明会导致问题

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

friend std::ostream& operator<<  <

operator<< 后面可以跟 < 的唯一方法是如果它是模板,则进行查找以检查它是否是模板。如果找到函数模板,则 < 被视为模板参数的开始。

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

...当我删除前向声明并在上面的代码中使用替代的朋友声明时。请注意,模板参数 U 不会出现在以下签名中...

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

一个简单的例子是:

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

void foo () {
  A a;
  operator<< <int> (a, 10);
}

...这段代码实际上正确吗?..

这有两个部分。首先,替代友元函数不会引用作用域中稍后的声明:

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!

友元函数实际上会在每个特化的命名空间中声明:

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<< 的每个特化 将有权根据其参数 test 的类型访问特定的专业化。因此,本质上,访问权限是根据您的需要进行限制的。然而,正如我之前提到的,这些函数基本上不能用作运算符,因为您必须使用函数调用语法:

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

根据上一个问题的答案,您可以使用 litb,或者按照 Dr_Asik的答案(这可能就是我会做的)。

更新:第二条评论

...更改类之前的前向声明;类中的那个仍然与我稍后实现的函数匹配...

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

friend std::ostream& operator<<  <

它通过查找名称并检查它是否是模板来实现这一点。只要您有一个虚拟的前向声明,那么这就会“欺骗”编译器将您的友元视为模板名称,因此 < 被视为模板参数列表的开头。

稍后,当您实例化该类时,您确实有一个可以匹配的有效模板。本质上,您只是欺骗编译器将友元视为模板特化。

您可以在这里执行此操作,因为(正如我之前所说),此时不会进行语义检查。

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

A friend function should be seen as a very special type of declaration. In essence, the compiler does enough to parse the declaration however no semantic checking will take place unless you actually specialize the class.

After making your suggested modification, if you then instantiate test you will get an error about the declarations not matching:

template class test<int>;

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

The compiler tries to parse the declaration to store it until the class template is specialized. During the parse, the compiler reaches the < in the declaration:

friend std::ostream& operator<<  <

The only way that operator<< could be followed by < is if it is a template, so a lookup takes place to check that it is a template. If a function template is found, then the < is considered to be the start of template arguments.

When you remove the forward declaration, no template is found and operator<< is considered to be an object. (This is also why when you add using namespace std the code continues to compile as there must be declarations of templates for 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...

There is no requirement that all template parameters be used in the arguments of a function template. The alternative declaration is for a new function template that will only be callable if declared in the namespace and specifying explicit template arguments.

A simple example of this would be:

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

void foo () {
  A a;
  operator<< <int> (a, 10);
}

...is this code actually correct?..

Well there are two parts to this. The first is that the alternative friend function does not refer to the declaration later in the scope:

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!

The friend function would actually be declared in the namespace for each specialization:

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

Every specialization of operator<< <U> will have access to the specific specialization as per the type of its parameter test<T>. So in essence the access is restricted as you require. However as I mentioned before these functions are basically unusable as operators, since you must use function call syntax:

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

As per the answers to the previous question, you either use the forward declaration as suggested by litb, or you go with defining the friend function inline as per Dr_Asik's answer (which would probably be what I would do).

UPDATE: 2nd Comment

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

As I pointed out above, the compiler checks if operator<< is a template when it sees the < in the declaration:

friend std::ostream& operator<<  <

It does this by looking up the name and checking if it is a template. As long as you have a dummy forward declaration then this "tricks" the compiler into treating your friend as a template name and so the < is considered to be the start of a template argument list.

Later, when you instantiate the class, you do have a valid template to match. In essence, you're just tricking the compiler into treating the friend as a template specialization.

You can do this here because (as I said earlier), no semantic checking takes place at this point in time.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文