在下面的C ++示例代码中,GCC 6和Clang 3.8不同意正确的行为:
这个人为的例子"工作" - 在test()
函数中返回GCC中的o.p
。在clang中,它调用(未定义的)函数get<int, int, float, double>
:
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);
bool test(const obj<int, float, double> &a) {
return get<int>(a);
}
在命名空间中放入相同的代码会导致GCC执行与clang相同的操作。
namespace ns {
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);
}
bool test(const ns::obj<int, float, double> &a) {
return ns::get<int>(a);
}
https://godbolt.org/g/sWrXQO和https://godbolt.org/g/9tIXwe
哪个编译器是&#34;正确&#34;通常有一种方法定义朋友成员模板函数内联,而不必声明它然后单独定义它。就是这样的事情:
struct Foo {
friend bool bar() { return true; } // declares *and* defines a free function bar
template<typename T> T bar2() { return true; } // doesn't work!
};
答案 0 :(得分:5)
关于类模板中定义的friend
函数模板,有两个未解决的问题:1545和2174。前者质疑它的有效程度,后者是基于这些功能模板的实际实例可能出现的odr违规。我不确定哪个编译器是正确的(之前认为两者都是错误的),但是在标准中可能只是在这种情况下正确行为的指标不正确或不正确。
代码应该理想地编译(待解决问题):
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);
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
}
这两个都应该工作 - 因为我们将函数重新声明为在命名空间范围内,我们甚至不必担心ADL。两个调用都应该找到::get<int>(obj<int, float, double> )
,这是friend
,因此代码应该编译和链接。 gcc允许不合格的呼叫而不是合格的呼叫,clang不允许。
我们完全可以通过 not 定义类中的函数模板来完全回避CWG问题:
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; }
使用这个公式,两个编译都允许 合格和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
作为普通类和类模板之间的规则中没有区别。