构造以下程序是为了滥用gcc / msvc中两阶段查找的某些功能。它可以与gcc / msvc和clang一起正常编译,但会导致函数g
的返回值不同:
struct A;
struct C {};
struct D {
D (const A &);
};
struct B {
void f (const C&,int){x=0;};
void f (const D&,char){x=1;};
int x;
};
template<typename T>
int f(const A &y)
{
B x;
x.f(y,0); // Line 18
return x.x;
}
struct A
{
operator C () const;
};
int g (const A&x)
{
return f<int>(x);
}
https://gcc.godbolt.org/z/pqAVsU
GCC和MSVC都调用A::operator C
并返回0,而Clang调用D(const A &)
并返回1。
按照标准,c是正确的,应该在尚未声明struct A
的情况下解决第18行的调用吗?或者是这种情况不明吗?
答案 0 :(得分:3)
[temp.dep.candidate]说,这可能与编译器失败有关(注释和重点是我的):
对于函数调用,其中 postfix-expression [即指定函数的表达式,此处
f
]是从属名称,使用常规查找找到候选函数规则([basic.lookup.unqual],[basic.lookup.argdep]),除了:
- [2PL / ADL行为的描述]
如果调用的格式不正确或将找到更好的匹配,如果在关联的命名空间中进行查找时考虑了所有在所有转换单元中的命名空间中引入的具有外部链接的函数声明,而不仅仅是考虑到在模板定义和模板实例化上下文中找到的那些声明,那么程序具有未定义的行为。
但是,x.f(y,0);
在任何时候都不依赖于T
,因此上述令人恐惧的段落不适用。在非依赖查找的情况下,由[temp.nondep](请注意我)覆盖:
使用常规名称查找找到模板定义中使用的非相关名称,并将其绑定到使用它们的位置。 [示例如下]
应在::f
定义的上下文中解析整个表达式,此时A
不完整,并且其转换运算符不可见。 Clang是正确的,GCC和MSVC都是错误的。
答案 1 :(得分:2)
根据[temp.res]/8,该程序格式错误,无需诊断:
可以在任何实例化之前检查模板的有效性。 [注:知道哪些名称是类型名称允许每个语法 以这种方式检查模板。 — [尾注]该程序是 格式错误,无需诊断,如果:
...
在假设实例化中这种构造的解释与对 模板的任何实际实例中的相应构造。 [注意:在以下情况下可能会发生这种情况:
在非依赖名称中使用的类型在定义模板时不完整,但在定义模板时不完整 实例化,或
...
...
在您的程序中,y
是一个非依赖名称,其类型A
在f
的定义中是不完整的,但是在实例化f<int>
时是完整的,因此以上规则适用。