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;
    }
    
    这是在类似的情况下(尽管他们希望避免的错误不同)在for
    enable_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;
    }