使用`enable_if`进行无限递归

Ruf*_*ind 6 c++ operator-overloading sfinae template-meta-programming

在尝试wrapper为另一种类型编写类型时T,我遇到了一个相当令人讨厌的问题:我想定义一些二进制运算符(例如+)将任何操作转发wrapper到基础类型,但我需要这些运算符接受任何潜在的涉及的组合wrapper:

wrapper() + wrapper()
wrapper() + T()
T()       + wrapper()
Run Code Online (Sandbox Code Playgroud)

天真的方法涉及直接编写所有潜在的重载.

但是我不喜欢编写重复的代码并且想要更多的挑战,所以我选择使用非常通用的模板来实现它并用一个限制潜在的类型enable_if.

我的尝试显示在问题的底部(对不起,这是我能想到的最小).问题是它会遇到无限递归错误:

  1. 为了评估test() + test(),编译会查看所有潜在的重载.
  2. 这里定义的运算符实际上是潜在的重载,因此它尝试构造返回类型.
  3. 返回类型有一个enable_if子句,它应该阻止它成为有效的重载,但编译器只是忽略它并尝试计算第decltype一个,这需要...
  4. ...的实例化operator+(test, test).

我们回到了我们开始的地方.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;
}
Run Code Online (Sandbox Code Playgroud)

这是相当笨重的解决方案,我宁愿不使用,除非我必须:

// 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);
Run Code Online (Sandbox Code Playgroud)

Jan*_*ann 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;
}
Run Code Online (Sandbox Code Playgroud)