Jos*_*Gao 13 c++ templates language-lawyer c++11
N3485的第14.6.4.2节规定了以下关于候选候选函数查找:
如果调用结果不正确或者找到更好的匹配,那么相关命名空间中的查找会考虑在所有翻译单元中的那些名称空间中引入外部链接的所有函数声明,而不仅仅是考虑模板定义和模板中的那些声明实例化上下文,然后程序有未定义的行为.
对于一个"形成错误"的呼叫到底意味着什么,以及如何通过查找选择一个不正确的呼叫?另外,如果考虑所有翻译单元,为什么会找到更好的匹配呢?
Jon*_*ely 14
呼叫"形成不良"究竟是什么意思
形式上,[defns.ill.formed]定义为格式错误,格式不正确,[defns.well.formed]定义格式良好的程序为:
C++程序根据语法规则,可诊断语义规则和单定义规则(3.2)构建.
因此,格式错误的调用是具有无效语法或可诊断错误的调用,例如传递错误数量的参数,或者无法转换为参数类型的参数,或者过载歧义.
如何通过查找选择格式错误的调用?
我认为它说"if(调用将是格式错误的||会找到更好的匹配)"在相关名称空间中的查找考虑了所有具有外部链接的函数声明...",这意味着如果考虑你有未定义的行为其他功能会找到相同或更好的匹配.同样好的匹配会使调用变得模糊,即格式不正确,更好的匹配会导致调用不同的函数.
因此,如果在另一个上下文中,调用将是模糊的或导致另一种错误,但由于仅考虑实例化和定义上下文中的一组有限的名称而成功,因此它是未定义的.而如果在另一种情况下呼叫会选择更好的匹配,这也是不确定的.
另外,如果考虑所有翻译单元,为什么会找到更好的匹配呢?
我认为规则的原因是不允许在两个不同的上下文中实例化相同的模板特化导致它调用两个不同的函数的情况,例如,如果在一个翻译单元中调用找到一个函数,而在另一个翻译单元中它发现一个不同的函数,您将获得相同模板的两个不同实例,这违反了ODR,链接器将只保留一个实例,因此链接器不保留的实例化将被调用函数的实例化替换.甚至可以看到模板被实例化的位置.
这与前一段的最后一句相似(如果尚未涵盖):
任何模板的特化可以在多个翻译单元中具有实例化点.如果两个不同的实例化点根据一个定义规则(3.2)给出模板特化不同的含义,则程序是不正确的,不需要诊断.
426页的C++ ARM(埃利斯和斯特劳斯)的给予一些背景的文本(我相信对于14.6.4.2和),并解释了它更简洁清晰,比我上面做的:
这似乎意味着从模板中使用的全局名称可以绑定到不同编译单元中的不同对象或函数,甚至绑定到编译单元内的不同点.但是,如果发生这种情况,生成的模板函数或类将被"单一定义"规则(第7.1.2节)变为非法.
[basic.def.odr]/6中有相同规则的另一个相关表述
问题是命名空间可以零敲定地定义,因此没有一个地方可以保证定义命名空间的所有成员.结果,不同的翻译单元可以看到不同的命名空间成员集.本节所说的是,如果未看到的部分会影响查找,则行为未定义.例如:
namespace mine {
void f(double);
}
mine::f(2); // seems okay...
namespace mine {
void f(char);
}
mine::f(2); // ambiguous, therefore ill-formed
Run Code Online (Sandbox Code Playgroud)
规则说第一次调用f(2)产生未定义的行为,因为如果mine在那一点上所有的重载都是可见的,它就会形成错误.
以@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
Run Code Online (Sandbox Code Playgroud)
用不同的优化级别编译该程序会更改其行为。根据标准,这没关系,因为“ 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)
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
3169 次 |
| 最近记录: |