带有“enable_if”的无限递归

Infinite recursion with `enable_if`

本文关键字:无限 递归 if enable 带有      更新时间:2023-10-16

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

wrapper() + wrapper()
wrapper() + T()
T()       + wrapper()

天真的方法包括直接编写所有潜在的重载。

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

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

  1. 为了评估test() + test(),编译会查看所有潜在的重载
  2. 这里定义的运算符实际上是一个潜在的重载,因此它试图构造返回类型
  3. 返回类型有一个enable_if子句,本应防止它成为有效的重载,但编译器忽略了这一点,并尝试首先计算decltype,这需要
  4. 。。。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*

注意:此代码依赖于Twrapper<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+