首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >为什么我不能使用显式模板参数调用模板朋友函数?

为什么我不能使用显式模板参数调用模板朋友函数?
EN

Stack Overflow用户
提问于 2020-03-07 18:16:56
回答 3查看 5.5K关注 0票数 4

请考虑以下示例:

代码语言:javascript
复制
struct S {
    template<typename T = void>
    friend void foo(S) {
    }
};

int main() {
    S s;
    foo(s); // (1)
    foo<void>(s); // (2)
}

我的GCC 9.2.0未能编译(2),错误如下:

代码语言:javascript
复制
a.cpp: In function 'int main()':
a.cpp:10:5: error: 'foo' was not declared in this scope
   10 |     foo<void>(s);
      |     ^~~
a.cpp:10:9: error: expected primary-expression before 'void'
   10 |     foo<void>(s);
      |         ^~~~

然而,(1)工作得很好。为什么会这样呢?如何使用显式模板参数调用foo

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2020-03-07 18:44:28

类体中的friend函数定义不会使friend函数在封闭的名称空间范围内显示到通常的非限定名称查找(尽管它们被放置在这个命名空间范围中)。

为了使其可见,您需要在命名空间范围中为模板添加一个声明(这在定义之前还是之后并不重要):

代码语言:javascript
复制
struct S {
    template<typename T = void>
    friend void foo(S) {
    }
};

template<typename T>
void foo(S);

int main() {
    S s;
    foo(s); // (1)
    foo<void>(s); // (2)
}

现在的问题是为什么foo(s)工作。这是因为依赖于参数的查找。在没有嵌套名称说明符的函数调用中,还搜索调用参数类型(以及其他类型)的类和包围名称空间,以查找匹配的函数重载。对于依赖于参数的查找,只能在类体中声明的friends是可见的。这样就找到了调用foo(s)的匹配函数。

foo<void>(s)应该以同样的方式工作,因为名称是不合格的,而且sS类型的,所以ADL应该再次在S中找到朋友foo

然而,还有另一个问题要考虑。当编译器读取foo时,它必须决定foo是否可以是一个模板,因为它在foo之后改变了对<的解析。

要确定这一点,需要在foo上进行不限定名称查找。在C++20之前,只有在查找找到某种类型的模板时,foo才会被视为模板名。但是,在您的情况下,不限定名查找没有发现任何内容,因为唯一的foo对于普通的非限定名查找是不可见的。因此,foo将不被视为模板,foo<void>也不会被解析为模板id。

在C++20中,规则被更改,如果不限定名称查找找到一个普通函数,或者完全不使用该名称,那么foo<void>也将被视为一个模板id。在这种情况下,调用的下面的ADL将找到foo,并且调用将成功。

因此,代码将像在C++20和pre++20中一样工作,实际上,您只需要声明任何名为foo的模板就可以得到foo<void>(s),以找到由ADL建立的好友foo。例如:

代码语言:javascript
复制
struct S {
  template <typename T = void>
  friend void foo(S) {}
};

template<int>
void foo();

int main() {
  S s;
  foo(s);        // (1)
  foo<void>(s);  // (2)
}
票数 4
EN

Stack Overflow用户

发布于 2020-03-07 18:38:39

Clang 在哥德波特能够提供一些启示:

代码语言:javascript
复制
<source>:9:5: warning: use of function template name with no prior declaration in function call with explicit template arguments is a C++20 extension [-Wc++20-extensions]

    foo<void>(s);
    ^

1 warning generated.

在C++20到达并修复它之前,这种语法似乎并不是语言的一部分。

由于foo()是在S结构中完全实现的,因此在main()中没有可见的模板foo。因此,编译器不理解它需要做ADL,并且无法找到foo()。比如@0x499602D2。

一种解决方案是更新编译器,另一种解决方案是在S之外实现S,还可以添加一个前向声明来提供默认的模板参数:

代码语言:javascript
复制
struct S;

template<typename T = void>
void foo(S); // (a)

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

template<typename T>
void foo(S) { // (c)
    // Needs S to be complete.
}

int main() {
    S s;
    foo(s);
    foo<void>(s);
}

如果您试图省略前向声明(a),您将发现您无法向(b)添加默认模板参数,因为是原因,而不能将其添加到(c),因为它是对(b)的重新声明,因此不能引入默认的模板参数。

票数 2
EN

Stack Overflow用户

发布于 2020-03-07 18:28:43

它是一个模板的事实并不重要。只有通过ADL查找才能找到在声明它们的位置定义的朋友函数。当您使用模板argments时,编译器试图查找一个名为foo的函数模板,该模板具有正常的不合格查找,但失败。foo(s)使用相关的s名称空间(全局名称空间)查找foo,并找到您定义的朋友函数。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/60580782

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档