当前位置: 首页 > 知识库问答 >
问题:

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

蒋权
2023-03-14

考虑以下示例:

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)并出现以下错误:

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?

共有3个答案

胡鸿志
2023-03-14
匿名用户

戈德博尔特的叮当声透露了一些信息:

<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.

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

由于foo()完全在S结构内部实现,main()中没有可见的模板foo。因此,编译器不明白它需要做ADL,也无法找到foo()。就像@0x499602D2说的。

一种解决方案是更新编译器,另一种解决方案是在S之外实现foo,并且可以选择添加转发声明以提供默认模板参数:

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)的重新声明,因此无法引入默认模板参数。

邢飞雨
2023-03-14

它是一个模板这一事实并不重要。只有通过ADL查找才能找到在声明位置定义的友元函数。当您使用模板参数时,编译器会尝试使用常规的非限定查找来查找名为foo的函数模板,但失败了foo(s)查找具有关联命名空间(全局命名空间)的foo(并查找您定义的友元函数)。

文华美
2023-03-14

类主体中的函数定义不会使函数在封闭的命名空间范围内可见,以进行常规的非限定名称查找(尽管它们被放置在此命名空间范围内)。

为了使其可见,您需要在命名空间范围内为模板添加声明(无论这发生在定义之前还是之后):

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)有效。这是因为依赖于参数的查找。在没有嵌套名称说明符的函数调用中,还将搜索调用参数类型的类和封闭名称空间(以及其他类型),以查找匹配的函数重载。对于依赖于参数的查找,仅在类主体中声明的s可见。这样就可以找到调用foo(s)的匹配函数。

<代码>foo

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

为了确定这一点,在foo上进行了不合格的名称查找。在C 20之前,只有当此查找找到具有该名称的某种模板时,foo才会被视为模板名称。但是不合格的名称查找在您的情况下找不到任何东西,因为唯一的foo对于普通的不合格名称查找是不可见的。因此foo将不被视为模板和foo

在C 20中,规则被更改了,如果非限定名称查找找到了一个使用该名称的普通函数,或者根本找不到任何函数,那么

因此,代码将与C 20和C 20之前的代码一样工作。实际上,您只需按名称声明任何模板,即可获得

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

 类似资料:
  • 我正在学习一个视频教程,我想声明一个模板函数作为模板类的朋友。我不知道为什么代码会抛出错误。 编译器抛出错误。 错误: templates\u friends\u 38。cpp:在“void doSomething2(T)[T=int]”的实例化中:templates\u friends\u 38。cpp:40:19:此处需要templates\u friends\u 38。cpp:32:9:错误

  • 我无法让它工作: 无法编译,错误消息如下: 无效使用temping-id'运算符*

  • 我有一个模板,其中有一个friend函数的声明,在类之外,我有它的实现: 在其他文件中的某个地方将其命名为什么签名? 我尝试过: 但它说它无法解决这个问题。为什么?朋友成员应该这样看待,不是吗? 编辑: 这是在Troll.cpp在它的功能。 仍会喊出“未在此范围内声明”、“函数无法解析”、“符号无法解析”、“之前应为主表达式”、“之前应为主表达式”

  • 我试图使乘法运算符成为名为TVector3的模板类的朋友。我读过,我可以在类声明中声明朋友函数之前,对其进行前向声明,但我这样做的尝试是徒劳的。我知道我可以简单地定义friend函数而不是声明它,但我希望它能与前向声明技术一起工作。 特别是,我试图为我的案例实施这个解决方案。我发现这篇文章也是David Rodriguez给出的解决方案(第三个版本),但我不知道我做错了什么。 我使用'g temp

  • 我有以下代码: 这段代码会导致编译错误。使用< code>g -std=c 1z编译时,错误显示如下: 使用< code>clang -std=c 1z时,错误为: 我在MSYS2 MinGW-w64环境中运行这些。我的GCC版本是GCC 7.1.0,我的Clang版本是4.0.0;我在GCC和Clang中使用的标准库是与我的GCC编译器捆绑在一起的libstdc。 在我看来,对函数templat

  • 我试图创建一个带有可选参数的模板函数,但我很难理解编译失败的原因。这是我的测试(设计)代码: 只有当我用这两个参数调用时,代码才会编译。如果我尝试只传递1个参数,编译将失败,出现以下错误: main.cpp:18:34:错误:调用'GetCountIf' std::cout<<“numelements=”< (我用的是C++14)