同一个文件中有两个函数
// 函数 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 也没鸟这条规定?
求解答。
1
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 是有歧义的, 后来修复了. |
2
geelaw 2018-05-09 01:47:10 +08:00 1
顺序是 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,所以此时只能找到第一个模板。 |
3
scinart OP @justou 多谢回复。
但是我还有一个问题: 我用-std=c++17 编译,理论上模板的重载解析已经修复了,但是为什么只有在其他函数中调用时运用这条规则(如在`h`函数中选择两种`f`),自身递归时不起作用呢(如在`p`中选择两种`p`) |
5
geelaw 2018-05-09 02:00:24 +08:00 via iPhone
@scinart 如果整段代码在一个 namespace 里面,S 和 SS 里面包括了该 namespace 里面的一个类,则会重新检查该 namespace。(我没试过,但文档是这个意思,似乎。)
|
6
gnaggnoyil 2018-05-09 03:28:11 +08:00
function/function template 的 overload resolution 是严格按照 point of declaration 的顺序来的,和 class template 的 specialization 的模式匹配规则不一样...
|
7
coordinate 2018-05-09 10:00:40 +08:00
我使用 vs2017 没有报错
|
8
ziv763 2018-05-09 10:58:59 +08:00
函数 1 处还看不到函数 2 的声明。
可以在函数 1 前 前置声明,或者将函数 2 搬到函数 1 上方,或者在 header 中声明,#include header。 |
9
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 歧义问题,不知能否给出具体页码. |
10
geelaw 2018-05-09 12:22:03 +08:00 via iPhone
@coordinate 因为正式版的 VS2017 还没有支持 ADL,一旦是模板,整个 look up 都会推迟到 instatiation 的阶段(更准确的说法是,不支持模板定义时刻的 non-ADL )
|