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

yep*_*ons 4 c++ templates friend friend-function

考虑以下示例:

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

int main() {
    S s;
    foo(s); // (1)
    foo<void>(s); // (2)
}
Run Code Online (Sandbox Code Playgroud)

我的 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);
      |         ^~~~
Run Code Online (Sandbox Code Playgroud)

不过,(1)效果很好。为什么是这样?如何foo使用显式模板参数进行调用?

wal*_*nut 6

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

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

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)
}
Run Code Online (Sandbox Code Playgroud)

现在的问题是为什么foo(s)有效。这是因为依赖于参数的查找。在没有嵌套名称说明符的函数调用中,还会搜索调用参数(和其他)类型的类和封闭名称空间以查找匹配的函数重载。对于friend仅在类主体中声明的参数相关查找是可见的。这样foo(s)就找到了与调用匹配的函数。

foo<void>(s)应该以同样的方式工作,因为名称是非限定的并且s是类型,所以 ADL 应该再次找到里面的S朋友。fooS

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

为了决定这一点,在 上完成了非限定名称查找foo。在 C++20 之前,foo仅当此查找找到某种具有该名称的模板时,才会被视为模板名称。但是,非限定名称查找在您的情况下找不到任何内容,因为唯一的foo对普通非限定名称查找不可见。因此foo不会被视为模板,也foo<void>不会被解析为 template-id。

在 C++20 中,规则已更改,如果非限定名称查找找到该名称的普通函数或根本找不到任何内容foo<void>,则也将被视为模板 ID。在这种情况下,将找到该呼叫的以下 ADL foo,并且呼叫将成功。

因此,代码将像 C++20 和 C++20 之前的版本一样工作,实际上您只需要按名称声明任何foo模板即可foo<void>(s)找到 ADL 的友元foo。例如:

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)
}
Run Code Online (Sandbox Code Playgroud)