C ++ 11:格式错误的调用是未定义的行为?

时间:2013-03-06 21:33:46

标签: c++ templates c++11 language-lawyer

来自N3485的

§14.6.4.2声明以下关于从属候选函数查找:

  

如果调用结果不正确或者找到更好的匹配,则相关命名空间内的查找会考虑所有翻译单元中这些命名空间中引入外部链接的所有函数声明,而不仅仅是考虑模板中找到的那些声明定义和模板实例化上下文,然后程序有未定义的行为。

对于一个“形成错误”的呼叫到底意味着什么,以及如何通过查找选择一个不正常的呼叫?另外,如果考虑所有翻译单位,为什么会找到更好的匹配呢?

4 个答案:

答案 0 :(得分:14)

  

呼叫“形成不良”究竟是什么意思

正式地,由于[defns.ill.formed]将格式错误定义为格式不正确,并且[defns.well.formed]将格式良好的程序定义为:

  

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

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

  

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

我认为它说“if(调用将是格式错误的||会找到更好的匹配)”如果相关名称空间中的查找被认为是所有具有外部链接的函数声明...“,这意味着你有未定义如果考虑其他功能会发现相同或更好的匹配。同样好的匹配会使调用变得模糊,即格式不正确,更好的匹配会导致调用不同的函数。

所以如果在另一个上下文中,调用会不明确或导致其他类型的错误,但由于在实例化和定义上下文中只考虑了一组有限的名称而成功,所以它是未定义的。而 if 在另一个上下文中,调用会选择更好的匹配,这也是未定义的。

  

另外,如果考虑所有翻译单位,为什么会找到更好的匹配呢?

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

前一段的最后一句话类似(如果尚未涵盖):

  

任何模板的特化都可能在多个翻译单元中具有实例化点。如果两个不同的实例化点根据一个定义规则(3.2)给出模板特化的不同含义,则该程序格式错误,无需诊断。

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

  

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

[basic.def.odr] / 6

中有相同规则的另一个相关表述

答案 1 :(得分: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中的所有重载都在那时可见,那么它就会形成错误。

答案 2 :(得分:5)

@tletnes' partial answer的基础上,我想我已经想出了一个简单的程序来触发这个特定的未定义行为。当然它使用多个翻译单元。

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)

答案 3 :(得分: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"); }