Infinite recursion with enable_if

In an attempt to write a wrapper type for another type T I ran into a rather unpleasant problem: I would like to define some binary operators (such as + ) that forward any wrapper operation to the base type, but I need these operators to accept any of the possible combinations that include wrapper :

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

A naive approach involves recording all potential overloads directly.

But I donโ€™t like writing duplicate code and need a little more complicated, so I decided to implement it using a very general template and limit the possible types with enable_if .

My attempt is shown at the bottom of the question (sorry, this is the minimum as I can imagine). The problem is that it will work in an infinite recursion error:

  • To evaluate test() + test() , the compiler looks at all potential overloads.
  • The operator defined here is actually a potential overload, so it is trying to build a return type.
  • The return type has an enable_if clause that should prevent it from actually overloading, but the compiler simply ignores this and tries to calculate decltype first, which requires ...
  • ... instance operator+(test, test) .

And we returned to where we started. GCC is good enough to spit out an error; Clang is just segfaults.

What would be a good, clean solution for this? (Keep in mind that there are other operators that need to follow the same pattern.)

 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 wt; } // 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; } 

Here is a rather clumsy solution that I would prefer not to use unless I want to:

 // 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); 
+6
source share
4 answers

As an addition to the TemplateRex comment, I would suggest using a macro to implement all overloads and take the operator as an argument:

 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; } 
+2
source

This is what is affected on the boost page for enable_if , in a completely similar situation (although the error they want to avoid is different). The boost solution was to create the lazy_enable_if class.

The problem is that the compiler will try to instantiate all types that are present in the function signature, and therefore the decltype(...) expression. There is also no guarantee that the condition is evaluated before the type.

Unfortunately, I could not find a solution to this problem; my last attempt can be seen here and still causes the maximum instance creation depth problem.

+2
source

The easiest way to write mixed arithmetic is to follow Scott Meyers Point 24 in Effective C ++

 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); } 

Note that you still need to write operator+= to provide a consistent interface ( "do as ints do ). If you also want to avoid this pattern, see 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); } 

Anyway you can write

 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"; } 

which will print 3 in all cases. Living example . Boost's approach is very general, for example. you can get from boost::arithmetic to also provide operator* from your definition of operator*= .

NOTE : this code uses T - wrapper<T> implicit conversions. But to quote Scott Meyers:

Classes that support implicit type conversions are generally bad ideas. Of course, there are exceptions to this rule, and one of the most common when creating numeric types.

+1
source

I have a better answer for your purpose: don't make it complicated, don't use a lot of metaprograms. Instead, use a simple function to expand and use normal expressions. You do not need to use enable_if to remove statements from the set of overload functions. If they are not used, you never need to compile , and if they are used, they give a meaning to the 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 wt; } 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; } 

Edit:

As an intermediate additional info I have to say, the orignal soultion from fzlogic compiles to msvc 11 (but not 10). Now my solution (presented here) does not compile on both (gives C1045). If you need to address these values โ€‹โ€‹with msvc and gcc (I don't have clang here), you need to move the logic to different namespaces and functions to prevent the compiler from using ADL and to take into account the operator+ .

+1
source

Source: https://habr.com/ru/post/951089/


All Articles