问一个 c++模板函数的问题

2018-05-09 00:48:20 +08:00
 scinart

同一个文件中有两个函数

// 函数 1
template <typename S, typename ... SS>
void p(S s, SS... ss){
    p(ss...);
}

// 函数 2
template <typename S>
void p(S s){
}

如上:上述代码在编译的时候报错,因为当第一个函数递归到 SS 为空 paramater pack 时,无法调用 p(ss...),但是如果将函数 1 与函数 2 的位置互换,则正常编译。

我的理解是,不换位置时,编译器只看到了函数 1,递归死,报错。换位置后,编译器知道了只有一个模板参数的 p,所以递归时直接用了,这样理解对不对?

另外我想问一下,这是编译器的个人行为,还是 c++标准里有规定的?

另外 http://en.cppreference.com/w/cpp/language/function_template 说:

template<class  T, class... U> void f(T, U...);           // #1
template<class  T            > void f(T);                 // #2
void h(int i) {
  f(&i);        // calls #2 due to the tie-breaker between parameter pack and no parameter
                // (note: was ambiguous between DR692 and DR1395)
}

但是 gcc 也没鸟这条规定?

求解答。

2501 次点击
所在节点    C
11 条回复
justou
2018-05-09 01:28:28 +08:00
你的理解是对的, 在编译期递归模板实例化的时候要看到递归的终止条件, 否则编译错;
一般这样写:

// 函数 2
template <typename S>
void p(S s){
}

// 函数 1
template <typename S, typename ... SS>
void p(S s, SS... ss){
p(s); // 处理第一个
p(ss...); // 处理剩余的
}

你用不同编译器测试一下看看结果是否一直就可以判断了, 我觉得这是 c++的规则;

第二个问题是模板的重载解析, 像这样两个函数模板只有一个 parameter pack 的区别, 在 f(&i)处没有 parameter pack 那个模板有较高的优先级;

重载解析的规则其实可以用"懒"来概括编译器的行为, 编译器: 想让我多干活? 那是不可能的. 所以在上面那种情况下实例化模板时, 编译器挑轻松的来做(不想碰那个有 parameter pack 模板), 要是遇到两个难易程度都差不多的重载, 编译器就直接罢工, 一个都不想做(有歧义, 就是不做), 我是这样来理解的. 这个在 C++ Templates - The Complete Guide, 2nd Edition 里面有讲, 还提到上面那种情况在最初的 C++11 和 C++14 是有歧义的, 后来修复了.
geelaw
2018-05-09 01:47:10 +08:00
顺序是 name look-up + template instantiation + overload resolution。

http://en.cppreference.com/w/cpp/language/unqualified_lookup 根据 template definition 一节

> For a dependent name used in a template definition, the lookup is postponed until the template arguments are known, at which time ADL examines function declarations [with external linkage (until C++11)] that are visible from the template definition context as well as in the template instantiation context, while **non-ADL lookup only examines function declarations [with external linkage (until C++11)] that are visible from the template definition context (in other words, adding a new function declaration after template definition does not make it visible except via ADL)**.

在你的第一段代码中,如果假设 S, SS... 里都是基本类型,则不存在 ADL,因此只有 non-ADL lookup,所以此时只能找到第一个模板。
scinart
2018-05-09 01:51:27 +08:00
@justou 多谢回复。

但是我还有一个问题:

我用-std=c++17 编译,理论上模板的重载解析已经修复了,但是为什么只有在其他函数中调用时运用这条规则(如在`h`函数中选择两种`f`),自身递归时不起作用呢(如在`p`中选择两种`p`)
scinart
2018-05-09 01:56:48 +08:00
@geelaw 感谢回复,解答了我刚刚问的问题。英语貌似看懂了,但是好像和是不是基本类型没关系?我明天再查查。
geelaw
2018-05-09 02:00:24 +08:00
@scinart 如果整段代码在一个 namespace 里面,S 和 SS 里面包括了该 namespace 里面的一个类,则会重新检查该 namespace。(我没试过,但文档是这个意思,似乎。)
gnaggnoyil
2018-05-09 03:28:11 +08:00
function/function template 的 overload resolution 是严格按照 point of declaration 的顺序来的,和 class template 的 specialization 的模式匹配规则不一样...
coordinate
2018-05-09 10:00:40 +08:00
我使用 vs2017 没有报错
ziv763
2018-05-09 10:58:59 +08:00
函数 1 处还看不到函数 2 的声明。

可以在函数 1 前 前置声明,或者将函数 2 搬到函数 1 上方,或者在 header 中声明,#include header。
maxco292
2018-05-09 11:49:40 +08:00
@geelaw

我测试了一下是没有问题的,如果最后一个参数是 namespace 里的一个类,会重新查找整个 namespace.而且这段代码在 C++11 下也是可以工作的.
https://wandbox.org/permlink/pQsUCjziHSShZD9J

@justou

我搜了一下全书,以·ambiguous·为关键词,但是好像没有找到你说的那个 C++11 和 C++14 歧义问题,不知能否给出具体页码.
geelaw
2018-05-09 12:22:03 +08:00
@coordinate 因为正式版的 VS2017 还没有支持 ADL,一旦是模板,整个 look up 都会推迟到 instatiation 的阶段(更准确的说法是,不支持模板定义时刻的 non-ADL )
justou
2018-05-09 12:28:25 +08:00
@maxco292 4.1.2 Overloading Variadic and Nonvariadic Templates, 有个注解 1, 搜 "[CoreIssue1395])" 应该可以直接定位到. 其实没大必要过于纠结这些细节了

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/453275

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX