有两个未解决的问题friend
类模板中定义的函数模板:1545 http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1545 and 2174 http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#2174。前者质疑其有效程度,后者涉及基于这些函数模板的实际实例可能出现的 ODR 违规。我不确定哪个编译器是正确的(之前认为两者都是错误的),但标准中可能只是未充分或不明确地指定在这种情况下正确的行为是什么。
代码should理想情况下编译(待解决问题):
template<typename ...Args>
class obj {
bool p = false;
template<typename T, typename... Args2>
friend T get(const obj<Args2...> &o) { return o.p; }
};
template<typename T, typename... Args>
T get(const obj<Args...> &o);
The friend
声明首先声明get
,因此这会创建最内层封闭命名空间的新成员:::get
。外部声明只是重新声明相同的函数,因此实际上只有一个::get
。来自[temp.over.link]:
如果两个函数定义包含以下内容,则两个涉及模板参数的表达式被视为等效
表达式将满足单一定义规则(3.2),除了用于命名的标记
只要用于在一个表达式中命名模板参数的标记是相同的,模板参数就可以不同
替换为在另一个表达式中命名相同模板参数的另一个标记。用于确定
两个从属名称 (14.6.2) 是否等效,仅考虑名称本身,而不考虑模板上下文中名称查找的结果。
使用不同的模板参数名称(Args...
vs Args2...
) 很好 - 函数模板的第二个声明::get
有效并允许查找找到它。
这让我们想到:
bool test(const obj<int, float, double> &a) {
#ifdef UNQUAL
return get<int>(a); // unqualified
#else
return ::get<int>(a); // qualified
#endif
}
这两个should工作 - 因为我们将函数重新声明在命名空间范围内,所以我们甚至不必担心 ADL。两次调用都应该找到::get<int>(obj<int, float, double> )
,这是一个friend
,因此代码应该编译和链接。 gcc 允许不合格的调用,但不允许合格的调用,clang 两者都不允许。
我们可以完全回避这两个 CWG 问题,方法是:not在类中定义函数模板:
template<typename ...Args>
class obj {
bool p = false;
template<typename T, typename... Args2>
friend T get(const obj<Args2...> &o);
};
template<typename T, typename... Args>
T get(const obj<Args...> &o) { return o.p; }
通过这个公式,两种编译都允许both合格和不合格的调用get
.
如果我们重写代码obj
是一个类而不是类模板,但其他条件相同:
class obj {
bool p = false;
template <class T>
friend T get(const obj& o) { return o.p; }
};
template <class T> T get(const obj& );
bool test(const obj& a) {
#ifdef UNQUAL
return get<int>(a);
#else
return ::get<int>(a);
#endif
}
两个编译器都允许这两种调用。但据我所知,两者之间的规则没有区别obj
是一个普通的类和一个类模板。