为什么ADL不能找到函数模板?

87

C++规范的哪一部分限制了参数相关查找不能在相关命名空间中找到函数模板?换句话说,为什么下面main中的最后一个调用无法编译?

namespace ns {
    struct foo {};
    template<int i> void frob(foo const&) {}
    void non_template(foo const&) {}
}

int main() {
    ns::foo f;
    non_template(f); // This is fine.
    frob<0>(f); // This is not.
}

这是否意味着您期望在不编写ns :: frob()的情况下使用frob()函数? - Simon
另一个数据点:对于 template<typename T> void bar(T) {},ADL 很好地工作,bar(f) 成功。 - sbi
3
@Huw: 刚被它咬了 :) 很有趣,明确的资格规则将ADL排除在外,我猜 :/ (翻译:刚被咬了,有趣的是明确的资格规则将ADL排除,我猜。) - Matthieu M.
1
@Matt:哈哈,我也是刚刚。编程界真小啊。 - GManNickG
3
现在它可以在C++20中工作了,感谢P0846 - user2023370
显示剩余2条评论
4个回答

90

这部分内容的解释如下:

C++标准03 14.8.1.6:

[注意:对于简单的函数名,即使该函数名在调用的范围内不可见,参数相关的查找(3.4.2)也适用。这是因为调用仍具有函数调用的语法形式(3.4.1)。但是当使用具有显式模板参数的函数模板时,除非在调用点上有一个名称为该名称的函数模板可见,否则调用没有正确的语法形式。如果没有这样的名称可见,则调用在语法上不是良好形式,参数相关查找不适用。如果某些名称可见,则应用参数相关查找并且其他命名空间中可能会找到其他函数模板。

namespace A {
  struct B { };
  template<int X> void f(B);
}
namespace C {
  template<class T> void f(T t);
}
void g(A::B b) {
  f<3>(b);    //ill-formed: not a function call
  A::f<3>(b); //well-formed
  C::f<3>(b); //ill-formed; argument dependent lookup
              // applies only to unqualified names
  using C::f;
  f<3>(b);    //well-formed because C::f is visible; then
              // A::f is found by argument dependent lookup
}

9
这个要求的理由是什么?看起来有点奇怪。我的意思是,语法形式与任何事情有什么关系呢? - Lightness Races in Orbit
22
Vandevoorde和Josuttis的第9.3.5节解释了为什么这是一个语法问题(采用OP的示例进行命名):“编译器在决定<3>是否为模板参数列表之前,无法确定f<3>(b)是否为函数调用参数。反过来,我们在找到f()作为模板之前无法确定<3>是否为模板参数列表。由于这个鸡生蛋的问题无法解决,表达式被解析为(f<3)>(b),这毫无意义。”请注意,这与成员函数模板的“模板”消歧语法类似。 - TemplateRex
9
有没有解决这个问题的提案?使用 template f<3>(b) 可能会是更好的语法? - balki
1
@AngelusMortis expression ( expressions... )(注意括号前缺少运算符)始终是一个函数调用,编译器一旦看到开放的括号就知道了。问题在于<既可以作为运算符,也可以作为模板参数列表的开始,编译器必须进行大量额外的解析才能弄清楚哪个是哪个(可能有一些代码排列方式无法明确地完成这个任务)。似乎标准作者选择将其视为非法,可能是为了节省编译器开发人员的时间。 - Miral
4
这个要求在C++20中被取消了,OP的代码现在是符合规范的 :) - Rakete1111
显示剩余3条评论

11
自C++20版本开始,ADL也可以与显式函数模板完美配合。以下是提议:P0846R0:不可见的ADL和函数模板

不再需要用户使用template关键字,而是对查找规则进行修订,这样一个名字如果普通查找产生没有结果或者找到一个或多个函数并且后面跟着一个"<",将被视为寻找到一个函数模板名称,并导致ADL的执行。

目前只有GCC 9实现了这个特性,所以你的示例可以编译。 演示实例

太棒了!现在可以在Clang和其他地方工作了。 - user2023370

5
我想稍微修改已接受的答案。虽然在OP问题中不太清楚,但标准(由Kornel引用)中的重要部分是这样的(强调我的):
但是当使用具有显式模板参数的函数模板时,调用就没有正确的语法形式。
因此,禁止依赖ADL并使用显式模板参数。不幸的是,使用非类型模板参数需要使用显式参数(除非它们有默认值)。
以下是显示此内容的示例代码:
#include <string>
#include <utility>

namespace C {
  struct B { };
  template<class T> void f(T t){}
}

void g(C::B b) {
  f(b);           // OK
  //f<C::B>(b);   // ill-formed: not a function call, but only 
                  //  because explicit template argument were used

  std::string s;
  move(s);                      // OK
  //move<std::string&>(s);      // Error, again because 
                                //  explicit template argument were used
  std::move<std::string&>(s);   // Ok
}

int main()
{
 C::B b;
 g(b);
}

0

编辑:不,这不对。请参见@Kornel的答案


我不是完全确定,但经过查阅 Stroustrup 的《C++ 程序设计语言》附录 C 第 13.8.4 节后,我认为这可能是原因。

由于 frob 是一个模板,你可以在调用它之后的某个时刻将其专门化为 i=0。这意味着实现留下了两种选择 frob 的可能方式,因为它似乎可以在实例化点或处理翻译单元结束时选择调用哪个 frob

所以,我认为问题出在你可以这样做:

namespace ns {
    struct foo {};
    template<int i> void frob(foo const&) {}
}

int main() {
    ns::foo f;
    frob<0>(f);
    return 0;
}

namespace ns {
    template<> void frob< 0 >(foo const&) { /* Do something different*/ }
}

2
不,去掉命名空间你仍然有问题,是吧?在C++中,使用后的特化是一个常见的问题,如果在声明之后,则不使用专门的形式。 - Kornel Kisielewicz
@Kornel:啊,是的,这给出了一个不同的错误,更符合我所描述的。好的,谢谢你指出来。 - Troubadour

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接