ADL为什么找不到功能模板?


83

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)2010年

是的,以非模板功能的方式。
休2010年

仅供参考,以上代码在Comeau中也失败:comeaucomputing.com/tryitout-添加using namespace ns;ns::资格通过编译。这是一个很好的问题。
fbrereto 2010年

3
@Huw:只是被它咬伤了:)有趣的是,我猜想显式的资格如何排除ADL:/
Matthieu M.

1
@Matt:哈哈,我也是。小型编程世界。
GManNickG 2011年

Answers:


86

本部分对此进行了解释:

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
@LightnessRacesinOrbit Vandevoorde&Josuttis中的9.3.5节解释了为什么这一个语法问题(以OP的示例命名):“编译器在确定模板参数列表f<3>(b)之前,不能将其确定为函数调用参数<3>。相反,<3>在我们找到模板之前,无法确定它是模板参数列表f()。因为无法解决此鸡和蛋问题,因此将该表达式解析为(f<3)>(b),这毫无意义。” 请注意,这类似于template成员函数模板的消歧语法。
TemplateRex

9
有解决此问题的建议吗? template f<3>(b)可能是更好的语法?
巴尔基2015年

1
@AngelusMortis表达式 ( expressions ... )(请注意,在括号之前缺少运算符)始终是一个函数调用,并且编译器在看到开放的括号后便立即知道这一点。这里的问题是,<它既可以充当运算符,又可以充当模板参数列表的开始,并且编译器必须进行大量额外的解析才能找出其中的一个(并且可能存在一些不可能的代码排列)明确地做)。看来标准作者被选为非法,也许是为了节省编译器开发人员的精力。
Miral

2
在C ++ 20中已经取消了此要求,并且OP的代码现在格式正确:)
Rakete1111 '18

8

从c ++ 20开始,adl在显式函数模板中也可以正常工作。建议如下: P0846R0:ADL和不可见的功能模板

代替要求用户使用template关键字,建议对查找规则进行修订,以便对其进行常规查找不会产生任何结果或找不到一个或多个函数的名称,然后将其后跟aa“ <”视为如果已找到功能模板名称,并且将导致执行ADL。

当前,只有GCC 9实现了此功能,因此您的示例可以编译。

live demo


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在实例化时在处理翻译单元结束时可以选择要调用的方法。

所以,我认为问题是您可以做到

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*/ }
}

1
不,使用命名空间后,您仍然遇到问题,不是吗?使用后的特殊化是C ++中的一个正常问题,如果在以后声明,则不使用特殊形式。
Kornel Kisielewicz

@Kornel:是的,这给出了一个不同的错误,与我所描述的相符。很公平,感谢您指出这一点。
Troubadour
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.