首页 > 解决方案 > 关于C++模板中自由函数名解析的问题

问题描述

该程序按预期工作:

#include <iostream>

template <typename T>
void output(T t) {
   prt(t);
}

struct It {
   It(int* p) : p(p) {}
   int* p;
};

void prt(It it) {
   std::cout << *(it.p) << std::endl;
}

int main() {
   int val = 12;
   It it(&val);
   output(it);    
   return 0;
}

当您编译并执行它时,它会按原样打印“12”。尽管输出模板函数所需的函数prt是在output之后定义的,但prt在实例化点是可见的,因此一切正常。

下面的程序和上面的程序很相似,但是编译失败:

#include <iostream>

template <typename T>
void output(T t) {
   prt(t);
}

void prt(int* p) {
   std::cout << (*p) << std::endl;
}

int main() {
   int val = 12;
   output(&val);
   return 0;
}

此代码尝试执行与前面示例相同的操作,但在 gcc 8.2 中失败并显示错误消息:

     'prt' was not declared in this scope, and no declarations were found by
 argument-dependent lookup at the point of instantiation [-fpermissive]

唯一改变的是传递给输出的参数是内置类型,而不是用户定义的类型。但我认为这对于名称解析并不重要。所以我的问题是:1)为什么第二个例子失败了?2)为什么一个例子失败而另一个成功?

标签: c++templates

解决方案


此处适用的标准规则可在[temp.dep.candidate]中找到:

对于后缀表达式是从属名称的函数调用,使用通常的查找规则([basic.lookup.unqual]、[basic.lookup.argdep])找到候选函数,除了:

  • 对于使用非限定名称查找的查找部分,仅找到来自模板定义上下文的函数声明。

  • 对于使用关联命名空间 ([basic.lookup.argdep]) 的查找部分,只能找到在模板定义上下文或模板实例化上下文中找到的函数声明。

在这两个示例中,非限定名称查找都找不到 的声明prt,因为在定义模板的点之前没有这样的声明。因此,我们继续进行依赖于参数的查找,它仅在参数类型的关联命名空间中查找。

It是全局命名空间的成员,因此全局命名空间是一个关联的命名空间,并且一个声明在模板实例化上下文中的该命名空间内可见。

指针类型与 typeU*具有相同的关联命名空间U,而基本类型根本没有关联的命名空间。因此,由于唯一的参数类型int*是指向基本类型的指针,因此没有关联的命名空间,并且依赖于参数的查找不可能在第二个程序中找到任何声明。

我不能确切地说为什么规则是这样设计的,但我猜其意图是模板应该使用它打算使用的特定声明的函数,或者使用函数作为可扩展的自定义点,但是那些用户定制需要与他们将使用的用户定义类型密切相关。否则,可以通过为某些特定情况提供更好的重载来更改真正意味着使用一个特定函数或函数模板声明的模板的行为。诚然,这更多是从模板定义上下文中至少有一个声明的角度来看,而不是当该查找根本找不到任何东西时,但是我们会遇到 SFINAE 指望找不到东西的情况,等等。


推荐阅读