我遇到了当前C++编译器(clang/gcc)确定名称是否依赖的方式不一致。在下面的示例中,A::f
是相关的,但::f
不是,导致使用后者时出错。
template<typename>
struct B
{
typedef int Type;
};
template<typename U>
static U f(U u);
template<typename T>
struct A
{
template<typename U>
static U f(U u);
typename B<decltype(f(0))>::Type m1; // typename required
B<decltype(::f(0))>::Type m2; // typename not required
};
不一致的部分是A::f
的声明不依赖于模板参数A
,这意味着似乎没有必要将其视为依赖名称。
C++11标准中的以下措辞似乎涵盖了这种行为:
[临时出发]/3
如果 id 表达式包含以下内容,则它与类型相关
- 通过名称查找与使用依赖类型声明的一个或多个声明相关联的标识符
[温度类型]/3
如果类型是
- 由任何依赖类型构造的复合类型
::f
的声明显然不依赖于,因为它的类型仅取决于其自己的模板参数。为什么要区别对待A::f
?
标准,f
实际上是不依赖的。
14.6.2.2 与类型相关的表达式 [temp.dep.expr]
3 如果 id 表达式包含以下内容,则它与类型相关
- 通过名称查找与一个或多个使用依赖类型声明的声明相关联的标识符,
这同样适用于全局模板函数,也适用于成员模板函数:完全不适用。U
的返回类型依赖于模板函数的定义,但对于调用方,f<int>
的函数类型已从 U(U)
转换为 int(int)
。无论如何,它不会解释为什么编译器以不同的方式对待这两种情况,也不会解释为什么非模板成员函数也被视为依赖函数。
- 依赖的模板 ID,
- 指定依赖类型的转换函数 ID,或
这些不适用。模板 ID 中没有必须始终存在的<
或>
,并且没有调用转换函数。
- 嵌套名称说明符或限定 ID,用于命名未知专业化的成员;
见下文。
或者,如果它命名当前实例化的静态数据成员,该成员的类型为"未知
T
的数组",用于某些T
(14.5.1.3)。
这也不适用:不涉及数组。
因此,这取决于f
是否是未知专业的成员。但事实并非如此:
14.6.2.1 从属类型
5 名称是未知专业化的成员,如果它是
- 其中 [...] 的限定 ID。
- 其中 [...] 的限定 ID。
- 一个 id 表达式,表示类成员访问表达式 (5.2.5) 中的成员,其中 [...]。
这些不能适用:f
既不是限定的,也不是类成员访问表达式的一部分。
由于f
可以依赖的唯一方法是它是未知专业化的成员,并且它不是未知专业化的成员,因此f
一定不是依赖的。
至于为什么编译器仍然将其视为依赖,我没有答案。要么我这里的答案的某些部分是错误的,编译器有错误,要么编译器遵循不同版本的C++标准。使用无论名称是否依赖都有效的示例进行测试,都会显示编译器处理中的一些变化:
#include <cstdio>
void f(const char *s, ...) { std::printf("%s: non-dependentn", s); }
struct S1 { };
template <typename T>
struct S2 {
static S1 a;
static S1 b() { return {}; }
template <typename U>
static U c() { return {}; }
static void z() {
f("S1()", S1()); // sanity check: clearly non-dependent
f("T()", T()); // sanity check: clearly dependent
f("a", a); // compiler agreement: non-dependent
f("b()", b()); // compiler disagreement: dependent according to GCC 4.8, non-dependent according to clang
f("c<T>()", c<T>()); // sanity check: clearly dependent
f("c<S1>()", c<S1>()); // compiler agreement: dependent
f("decltype(b())()", decltype(b())()); // compiler agreement: dependent
}
};
void f(const char *s, S1) { std::printf("%s: dependentn", s); }
// Just to show it's possible to specialize the members
// without specializing the full template.
template <>
S1 S2<S1>::b() { return {}; }
template <>
template <>
S1 S2<S1>::c<S1>() { return {}; }
int main() {
S2<S1>::z();
}
叮叮对待b()
、decltype(b())()
和c<S1>()
的这种差异尤其令我感到不安。这没有任何意义。他们显然都是同样依赖的。我可以从实现的角度来看理解,必须注意不要为成员函数生成代码,因为可能存在S2<S1>::b
或S2<S1>::c<S1>
的专用化,但这适用于所有人,并且对返回类型没有影响。