为了尝试为另一种类型wrapper
编写T
类型,我遇到了一个相当令人讨厌的问题:我想定义一些转发的二元运算符(例如+
) wrapper
对基础类型的任何操作,但我需要这些运算符接受涉及wrapper
的任何潜在组合:
wrapper() + wrapper()
wrapper() + T()
T() + wrapper()
天真的方法涉及直接编写所有潜在的重载。
但我不喜欢编写重复的代码并想要更多的挑战,所以我选择使用非常通用的模板来实现它,并使用enable_if
来限制潜在的类型。
我的尝试显示在问题的底部(抱歉,这是我能想到的最小)。问题是它会遇到无限递归错误:
test() + test()
,编译会查看所有可能的重载。enable_if
子句,它应该阻止它成为有效的重载,但编译器只是忽略它并尝试首先计算decltype
,这需要...... operator+(test, test)
的实例化。我们回到了我们开始的地方。 GCC非常适合吐出错误; Clang只是段错误。
对此有什么好的 clean 解决方案? (请记住,还有其他运营商需要遵循相同的模式。)
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);
答案 0 :(得分:2)
作为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;
}
答案 1 :(得分:2)
这是enable_if
lazy_enable_if
上decltype(...)
所触及的,在一种完全相似的情况下(尽管他们希望避免的错误是不同的)。 boost的解决方案是创建一个{{1}}类。
问题在于,编译器将尝试实例化函数签名中存在的所有类型,因此也会{{1}}表达式。也不能保证在类型之前计算条件。
不幸的是我无法找到解决这个问题的方法;我的最新尝试可以看到boost page并仍然触发最大的实例化深度问题。
答案 2 :(得分:1)
编写混合模式算法最直接的方法是遵循Scott Effective 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+=
才能提供一致的界面(&#34;执行注意&#34; )。如果您还想避免使用该样板,请查看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。 Live example。 Boost方法非常普遍,例如您可以从boost::arithmetic
派生,也可以从operator*
的定义中提供operator*=
。
注意:此代码依赖于T
到wrapper<T>
的隐式转换。但引用Scott Meyers的话说:
支持隐式类型转换的类通常是个坏主意。 当然,这个规则有例外,也是最常见的规则之一 常见的是在创建数字类型时。
答案 3 :(得分:1)
我为你的目的找到了一个更好的答案:不要让它变得复杂,不要使用太多的元编程。而是使用简单的函数来解包和使用普通表达式。您不需要使用enable_if
从函数重载集中删除操作符。如果不使用它们,则永远不需要compile,如果使用它们,则会提供有意义的error。
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的orignal soultion在msvc 11下编译(但不是10)。现在我的解决方案(这里介绍)不能在两者上编译(给出C1045)。如果你需要用msvc和gcc来解决这些问题(我这里没有clang)你必须将逻辑移动到不同的命名空间和函数,以防止编译器使用ADL并考虑模板operator+
。 / p>