13

N3485 的第 14.6.4.2 节规定了以下有关依赖候选函数查找的内容:

如果调用格式错误或找到更好的匹配,则在关联命名空间中的查找考虑所有翻译单元中这些命名空间中引入的具有外部链接的所有函数声明,而不仅仅是考虑在模板定义和模板中找到的那些声明实例化上下文,则程序具有未定义的行为。

调用“格式错误”究竟意味着什么,查找如何选择格式错误的调用?另外,如果考虑到所有翻译单元,为什么找到更好的匹配很重要?

4

4 回答 4

14

呼叫“格式错误”到底意味着什么

形式上,[defns.ill.formed] 将格式错误定义为格式不正确,[defns.well.formed] 将格式正确的程序定义为:

根据语法规则、可诊断语义规则和单一定义规则 (3.2) 构建的 C++ 程序。

因此,格式错误的调用是具有无效语法或可诊断错误的调用,例如传递错误数量的参数,或无法转换为参数类型的参数,或重载歧义。

查找如何选择格式错误的调用?

我认为它的意思是“如果(调用将不正确 || 会找到更好的匹配)在相关命名空间中的查找考虑了所有具有外部链接的函数声明......”,这意味着如果考虑到你有未定义的行为其他函数会找到相等或更好的匹配。同样好的匹配会使调用模棱两可,即格式错误,而更好的匹配会导致调用不同的函数。

因此,如果在另一个上下文中调用会模棱两可或导致另一种错误,但由于仅在实例化和定义上下文中考虑一组有限的名称而成功,则它是未定义的。如果另一个上下文中调用会选择更好的匹配,那也是未定义的。

另外,如果考虑到所有翻译单元,为什么找到更好的匹配很重要?

我认为该规则的原因是不允许在两个不同的上下文中实例化相同的模板特化导致它调用两个不同的函数的情况,例如,如果在一个翻译单元中调用找到一个函数,而在另一个翻译单元中找到不同的函数函数,您将获得同一模板的两个不同实例化,这违反了 ODR,并且链接器只会保留一个实例化,因此链接器未保留的实例化将被调用的函数替换'甚至在模板被实例化的地方都不可见。

这与上一段的最后一句话相似(如果尚未涵盖):

任何模板的特化都可能在多个翻译单元中具有实例化点。如果根据一个定义规则(3.2),两个不同的实例化点赋予模板特化不同的含义,则程序是非良构的,不需要诊断。

C++ ARM (Ellis & Stroustrup) 的第 426 页为该文本提供了一些上下文(我相信 14.6.4.2 也是如此),并比我上面所做的更简洁明了地解释它:

这似乎意味着从模板中使用的全局名称可以绑定到不同编译单元中的不同对象或函数,甚至可以绑定到编译单元中的不同点。但是,如果发生这种情况,生成的模板函数或类将被“单一定义”规则(第 7.1.2 节)视为非法。

[basic.def.odr]/6 中还有另一个相同规则的相关表述

于 2013-03-06T22:03:05.373 回答
8

问题是命名空间可以零碎地定义,所以没有一个地方可以保证定义一个命名空间的所有成员。因此,不同的翻译单元可以看到不同的命名空间成员集。本节所说的是,如果未看到的部分会影响查找,则行为未定义。例如:

namespace mine {
    void f(double);
}

mine::f(2); // seems okay...

namespace mine {
    void f(char);
}

mine::f(2); // ambiguous, therefore ill-formed

该规则说,第一次调用会f(2)产生未定义的行为,因为如果此时所有的重载mine都可见,那么它将是不正确的。

于 2013-03-06T23:17:48.087 回答
5

基于@tletnes 的部分答案,我想我想出了一个简单的程序来触发这种特殊的未定义行为。当然,它使用多个翻译单元。

cat >alpha.cc <<EOF
#include <stdio.h>
void customization_point(int,int) { puts("(int,int)"); }
#include "beta.h"
extern void gamma();
int main() {
    beta(42);
    gamma();
}
EOF

cat >gamma.cc <<EOF
#include <stdio.h>
void customization_point(int,double) { puts("(int,double)"); }
#include "beta.h"
void gamma() { beta(42); }
EOF

cat >beta.h <<EOF
template<typename T>
void beta(T t) {
    customization_point(t, 3.14);
}
EOF

用不同的优化级别编译这个程序会改变它的行为。根据标准,这没关系,因为“alpha.cc”中的调用会调用未定义的行为。

$ clang++ alpha.cc gamma.cc -O1 -w ; ./a.out
(int,int)
(int,int)
$ clang++ alpha.cc gamma.cc -O2 -w ; ./a.out
(int,int)
(int,double)
于 2013-03-06T22:29:27.267 回答
1

当我阅读这条规则时,我想类似于以下的代码至少是正在考虑的部分:

int foo(int a; int b){ printf("A"); }

int main(){
   foo(1, 1.0);
}

int foo(int a, double b){ printf("B"); }

或者

int foo(int a);

int main(){
   foo(1);
}

int foo(int a, double b){ printf("B"); }
于 2013-03-06T22:13:54.670 回答