带有“enable_if”的无限递归
Infinite recursion with `enable_if`
在尝试为另一个类型T
编写wrapper
类型时,我遇到了一个相当令人讨厌的问题:我想定义一些二进制运算符(如+
),将wrapper
上的任何操作转发到底层类型,但我需要这些运算符接受任何涉及wrapper
:的潜在组合
wrapper() + wrapper()
wrapper() + T()
T() + wrapper()
天真的方法包括直接编写所有潜在的重载。
但我不喜欢写重复的代码,并且希望有更多的挑战,所以我选择使用一个非常通用的模板来实现它,并用enable_if
限制潜在的类型。
我的尝试显示在问题的底部(对不起,这是我能想到的最小的)。问题是它会遇到一个无限递归错误:
- 为了评估
test() + test()
,编译会查看所有潜在的重载 - 这里定义的运算符实际上是一个潜在的重载,因此它试图构造返回类型
- 返回类型有一个
enable_if
子句,本应防止它成为有效的重载,但编译器忽略了这一点,并尝试首先计算decltype
,这需要 - 。。。CCD_ 10的实例化
我们又回到了起点。GCC足够好,可以抛出一个错误;Clang只是插科打诨。
什么是一个好的清洁解决方案?(请记住,也有其他运营商需要遵循相同的模式。)
template<class T>
struct wrapper { T t; };
// Checks if the type is instantiated from the wrapper
template<class> struct is_wrapper : false_type {};
template<class T> struct is_wrapper<wrapper<T> > : true_type {};
// Returns the underlying object
template<class T> const T& base(const T& t) { return t; }
template<class T> const T& base(const wrapper<T>& w) { return w.t; }
// Operator
template<class W, class X>
typename enable_if<
is_wrapper<W>::value || is_wrapper<X>::value,
decltype(base(declval<W>()) + base(declval<X>()))
>::type operator+(const W& i, const X& j);
// Test case
struct test {};
int main() {
test() + test();
return 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);
作为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;
}
在enable_if
的提升页面上,在一个完全相似的情况下(尽管他们希望避免的错误不同),会提到这一点。boost的解决方案是创建一个lazy_enable_if
类。
事实上,问题是编译器将尝试实例化函数签名中存在的所有类型,从而实例化decltype(...)
表达式。也不能保证在类型之前计算条件。
不幸的是,我没能想出解决这个问题的办法;我的最新尝试可以在这里看到,并且仍然会触发最大实例化深度问题。
编写混合模式算术的最简单方法是遵循有效C++中的Scott Meyers的第24项
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);
}
请注意,您仍然需要编写operator+=
,以便提供一致的接口("像int那样做")。如果您还想避免这种样板,请查看Boost.Operators
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";
}
在所有情况下都将打印3现场示例。Boost方法非常通用,例如,您可以从boost::arithmetic
派生,也可以从您对operator*=
的定义中提供operator*
。
注意:此代码依赖于T
到wrapper<T>
的隐式转换。但引用Scott Meyers的话:
支持隐式类型转换的类通常是个坏主意。当然,这个规则也有例外常见的是在创建数字类型时。
对于您的目的,我有一个更好的答案:不要让它变得复杂,不要使用太多的元编程。相反,使用简单的函数来打开包装并使用普通表达式。您不需要使用enable_if
从函数重载集中删除to运算符。如果不使用它们,则永远不需要编译,如果使用,则会出现有意义的完全错误。
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;
}
编辑:
作为一个有趣的补充信息,我不得不说,fzlogic的原始灵魂是在msvc11下编译的(但不是10)。现在,我的解决方案(在这里给出)并不能在两者上编译(给出C1045)。如果你需要用msvc和gcc来处理这些isu(我这里没有clang),你必须将逻辑移动到不同的名称空间和函数,以防止编译器使用ADL并考虑模板化的operator+
。
- 交换运算符 + 重载会导致无限递归
- 为什么当函数参数未定义为常量引用时存在无限递归?
- 为什么这是无限递归
- 尝试"复制"shared_ptr向上转换行为会导致复制构造函数上的无限递归(导致段错误)
- ConstexPR模板功能的无限递归
- 交换和移动无限递归
- 无限循环与无限递归.两者都未定义吗?
- 函数中的无限递归
- 运算符重载流提取运算符 (>>) C++会导致无限递归流提取
- C :Ostream无限递归
- 无限递归模板实例化使用clang时GCC工作正常
- 当我使用 boost 构建绝对路径时,无限递归
- 模板中的无限递归
- C++中的无限递归快速排序
- 尽管有停止条件,无限递归调用仍会执行,因为参数不会前进
- 在映射中插入地址时,新运算符重载会导致无限递归
- 可变参数模板实例化中的无限递归,试图构建任意深度的树状结构
- 调用原始版本的DLL挂钩函数时,我得到了无限递归
- std::swap 在 VS 2013 中导致无限递归
- 卡在无限递归中