C++ 带有“enable_if”的无限递归`
在尝试为另一个类型C++ 带有“enable_if”的无限递归`,c++,operator-overloading,template-meta-programming,sfinae,C++,Operator Overloading,Template Meta Programming,Sfinae,在尝试为另一个类型T编写包装器类型时,我遇到了一个相当讨厌的问题:我想定义一些二进制运算符(例如+),将包装器上的任何操作转发到底层类型,但是我需要这些操作符接受任何涉及包装器的潜在组合: wrapper() + wrapper() wrapper() + T() T() + wrapper() 天真的方法包括直接编写所有潜在的重载 但是我不喜欢编写重复的代码,并且希望有更多的挑战,所以我选择使用一个非常通用的模板来实现它,并使用enable\u if限制潜在的类型 我的尝试显示在
T
编写包装器
类型时,我遇到了一个相当讨厌的问题:我想定义一些二进制运算符(例如+
),将包装器上的任何操作转发到底层类型,但是我需要这些操作符接受任何涉及包装器的潜在组合:
wrapper() + wrapper()
wrapper() + T()
T() + wrapper()
天真的方法包括直接编写所有潜在的重载
但是我不喜欢编写重复的代码,并且希望有更多的挑战,所以我选择使用一个非常通用的模板来实现它,并使用enable\u if
限制潜在的类型
我的尝试显示在问题的底部(对不起,这是我能想到的最低限度)。问题是它将遇到无限递归错误:
要评估test()+test()
,编译将查看所有潜在的重载
这里定义的运算符实际上是一个潜在的重载,因此它尝试构造返回类型
返回类型有一个enable\u if
子句,它应该防止它成为有效的重载,但编译器只是忽略了这一点,并尝试首先计算decltype
,这需要
。。。操作符+(test,test)
的实例化
我们又回到了起点。GCC足够好,可以吐出一个错误;叮当声只是一个小错误
有什么好的、干净的解决方案?(请记住,还有其他操作员需要遵循相同的模式。)
模板
结构包装器{T;};
//检查类型是否从包装器实例化
模板结构是_包装器:false_type{};
模板结构是_包装器:true_类型{};
//返回基础对象
模板常量T&base(常量T&T){return T;}
模板常量T&base(常量包装器&w){return w.T;}
//接线员
模板
typename启用\u如果<
is|wrapper::value | is|wrapper::value,
decltype(base(declval())+base(declval())
>::类型运算符+(常量W&i、常量X&j);
//测试用例
结构测试{};
int main(){
test()+test();
返回0;
}
这是一个相当笨重的解决方案,我宁愿不使用,除非我必须:
// Force the evaluation to occur as a 2-step process
template<class W, class X, class = void>
struct plus_ret;
template<class W, class X>
struct plus_ret<W, X, typename enable_if<
is_wrapper<W>::value || is_wrapper<X>::value>::type> {
typedef decltype(base(declval<W>()) + base(declval<X>())) type;
};
// Operator
template<class W, class X>
typename plus_ret<W, X>::type operator+(const W& i, const X& j);
//强制执行两步计算过程
模板
结构加网;
模板
struct plus_ret::type>{
typedef decltype(base(declval())+base(declval())类型;
};
//接线员
模板
typename plus_ret::type operator+(常量W&i、常量X&j);
作为TemplateRex注释的补充,我建议使用宏实现所有重载,并将运算符作为参数:
template<class T>
struct wrapper { T t; };
#define BINARY_OPERATOR(op) \
template<class T> \
T operator op (wrapper<T> const& lhs, wrapper<T> const& rhs); \
template<class T> \
T operator op (wrapper<T> const& lhs, T const& rhs); \
template<class T> \
T operator op (T const& lhs, wrapper<T> const& rhs);
BINARY_OPERATOR(+)
BINARY_OPERATOR(-)
#undef BINARY_OPERATOR
// Test case
struct test {};
test operator+(test const&, test const&);
test operator-(test const&, test const&);
int main() {
test() + test();
wrapper<test>() + test();
test() - wrapper<test>();
return 0;
}
模板
结构包装器{T;};
#定义二进制_运算符(op)\
模板\
T运算符op(包装常量和lhs、包装常量和rhs)\
模板\
T操作员op(包装常数和左侧、T常数和右侧)\
模板\
T操作员op(T常量和lhs、包装常量和rhs);
二进制_运算符(+)
二进制_运算符(-)
#未定义二元算子
//测试用例
结构测试{};
测试运算符+(测试常量和,测试常量和);
测试运算符-(测试常量&,测试常量&);
int main(){
test()+test();
包装器()+测试();
test()-wrapper();
返回0;
}
这是在类似的情况下(尽管他们希望避免的错误不同)在forenable_if
上涉及的内容。boost的解决方案是创建一个lazy\u enable\u if
类
问题是,编译器将尝试实例化函数签名中存在的所有类型,因此decltype(…)
表达式也是如此。也不能保证条件是在类型之前计算的
不幸的是,我无法想出解决这个问题的办法;可以看到我最近的尝试,但仍然引发了最大实例化深度问题。编写混合模式算法最简单的方法是遵循Scott Meyers在中的第24项
在这两种情况下,您都可以编写
int main()
{
wrapper1<int> v{1};
wrapper1<int> w{2};
std::cout << (v + w) << "\n";
std::cout << (1 + w) << "\n";
std::cout << (v + 2) << "\n";
wrapper2<int> x{1};
wrapper2<int> y{2};
std::cout << (x + y) << "\n";
std::cout << (1 + y) << "\n";
std::cout << (x + 2) << "\n";
}
intmain()
{
包装器1 v{1};
包装器1 w{2};
std::cout对于你的目的,我有一个更好的答案:不要让它变得复杂,不要使用太多的元编程。而是使用简单的函数来展开和使用正规表达式。你不需要使用enable_if
从函数重载集中删除to运算符。如果不使用它们,将永远不需要,如果使用他们给了一个有意义的回答
名称空间w{
模板
结构包装器{T;};
模板
T常量和展开(T常量和展开){
返回t;
}
模板
T常量和展开(包装常量和w){
返回w.t;
}
模板
自动运算符+(T1常量和T1,T2常量和T2)->取消类型(展开(T1)+展开(T2)){
返回展开(t1)+展开(t2);
}
模板
自动运算符-(T1常量和T1,T2常量和T2)->解类型(展开(T1)-展开(T2)){
返回展开(t1)-展开(t2);
}
模板
自动运算符*(T1常量和T1,T2常量和T2)->解类型(展开(T1)*展开(T2)){
返回展开(t1)*展开(t2);
}
}
//测试用例
结构测试{};
测试运算符+(测试常量和,测试常量和);
测试运算符-(测试常量&,测试常量&);
int main(){
test()+test();
w::wrapper()+w::wrapper();
w::wrapper()+test();
test()-w::wrapper();
返回0;
}
编辑:
作为一个有趣的补充信息,我必须说,fzlogic的原始解析是在msvc 11(而不是10)下编译的。现在我的解决方案(在这里展示)不是在这两个平台上编译的(给出C1045)。如果你需要用msvc和gcc解决这些问题(我这里没有clang),你必须将逻辑移到不同的名称空间和函数中,以实现pr
template<class T>
class wrapper1
{
public:
wrapper1(T const& t): t_(t) {} // yes, no explicit here
friend wrapper1 operator+(wrapper1 const& lhs, wrapper1 const& rhs)
{
return wrapper1{ lhs.t_ + rhs.t_ };
}
std::ostream& print(std::ostream& os) const
{
return os << t_;
}
private:
T t_;
};
template<class T>
std::ostream& operator<<(std::ostream& os, wrapper1<T> const& rhs)
{
return rhs.print(os);
}
template<class T>
class wrapper2
:
boost::addable< wrapper2<T> >
{
public:
wrapper2(T const& t): t_(t) {}
// operator+ provided by boost::addable
wrapper2& operator+=(wrapper2 const& rhs)
{
t_ += rhs.t_;
return *this;
}
std::ostream& print(std::ostream& os) const
{
return os << t_;
}
private:
T t_;
};
template<class T>
std::ostream& operator<<(std::ostream& os, wrapper2<T> const& rhs)
{
return rhs.print(os);
}
int main()
{
wrapper1<int> v{1};
wrapper1<int> w{2};
std::cout << (v + w) << "\n";
std::cout << (1 + w) << "\n";
std::cout << (v + 2) << "\n";
wrapper2<int> x{1};
wrapper2<int> y{2};
std::cout << (x + y) << "\n";
std::cout << (1 + y) << "\n";
std::cout << (x + 2) << "\n";
}
namespace w {
template<class T>
struct wrapper { T t; };
template<class T>
T const& unwrap(T const& t) {
return t;
}
template<class T>
T const& unwrap(wrapper<T> const& w) {
return w.t;
}
template<class T1,class T2>
auto operator +(T1 const& t1, T2 const& t2) -> decltype(unwrap(t1)+unwrap(t2)) {
return unwrap(t1)+unwrap(t2);
}
template<class T1,class T2>
auto operator -(T1 const& t1, T2 const& t2) -> decltype(unwrap(t1)-unwrap(t2)) {
return unwrap(t1)-unwrap(t2);
}
template<class T1,class T2>
auto operator *(T1 const& t1, T2 const& t2) -> decltype(unwrap(t1)*unwrap(t2)) {
return unwrap(t1)*unwrap(t2);
}
}
// Test case
struct test {};
test operator+(test const&, test const&);
test operator-(test const&, test const&);
int main() {
test() + test();
w::wrapper<test>() + w::wrapper<test>();
w::wrapper<test>() + test();
test() - w::wrapper<test>();
return 0;
}