我有以下课程:
template<int P>
struct A {
struct B {
auto abs() const { return 1; }
};
};
具体来说,A
应该是模P的整数有限域,而B
是表示来自该有限域的元素的类。然后我有扩展GCD算法的通用实现,它应该使用abs
函数。问题我希望扩展的GCD算法实现能够处理基本类型(int,long等),以及A<P>::B
。所以我需要实现一个非成员abs
来调用A<P>::B::abs
。我尝试了三种技术,其中两种不起作用,另一种起作用但不合适。
技术1(不起作用):使用依赖于参数的查找(ADL)。在A
的正文中定义以下内部,但在B
的正文之外:
auto abs(B const &e) { return e.abs(); }
由于ADL只在名称空间内而不是类范围内查找,所以它不会起作用。 更新:ADL在此处不起作用,因为abs
是一种实例方法。但是根据T.C。的回答,如果你把它变成了朋友的功能,它可以正常运行,ADL就可以找到它!
技术2(不起作用):在全局或命名空间范围内使用约束函数模板:
template<int P>
auto abs(typename A<P>::B const &e) { return e.abs(); }
这也不起作用,因为P
位于非推断的上下文中。
技术3(有效但不令人满意):在全局或命名空间范围内使用无约束函数模板:
template<typename T>
auto abs(T const &e) { return e.abs(); }
这很有效。但是,它不受约束。我想将此函数模板的实例化仅限制为A<P>
(事先为P
未知),以及其他类模板(用于多项式环和字段扩展)。问题是如果P
在非推断的上下文中,我不确定如何使用SFINAE。
答案 0 :(得分:3)
template<int P>
struct A {
struct B {
auto abs() const { return 1; }
};
friend auto abs(B const &e) { return e.abs(); }
// ^^^^^^
};
然后让ADL发挥其魔力。
(在B
内定义也没关系。选择取决于你。)
答案 1 :(得分:0)
这将有效:
namespace impl {
template <int P>
struct B {
auto abs() const { return 1; }
};
template <int P>
auto abs(B<P>& b) {
return b.abs();
}
}
template <int P>
struct A {
using B = impl::B<P>;
friend B;
};
int main() {
A<3>::B inner;
abs(inner);
}
此解决方案使用ADL,因为abs
现在与B
位于同一名称空间中。此外,我们通过将模板参数再次传递给P
,避免在未推断的上下文中P
(::
不在某个B
符号的左侧)。很遗憾,现在B
不是A
的成员,因此如果您想让它访问A
的成员,您将不得不将其变成朋友。此前的解决方案归因于this answer。我们接下来会改进它。
或者,如果您真的希望B
成为A
的成员,那么您只需要公共外观以确保正确的重定向:
namespace impl {
template <typename T>
struct facade : public T {};
template <typename T>
auto abs(facade<T>& b) { return b.abs(); }
}
template <int P>
struct A {
struct B_ {
auto abs() const { return 1; }
};
using B = impl::facade<B_ >;
};
int main() {
A<3>::B inner;
abs(inner);
}
这里的优势在于,这个公共门面可以重复使用几个课程,并且它不一定是朋友。