Question

In an attempt to write a wrapper type for another type T, I encountered a rather obnoxious problem: I would like to define some binary operators (such as +) that forward any operation on wrapper to the underlying type, but I need these operators accept any of the potential combinations that involve wrapper:

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

The naive approach involves writing all the potential overloads directly.

But I don't like writing duplicated code and wanted a bit more challenge, so I chose to implement it using a very generic template and restrict the potential types with an enable_if.

My attempt is shown at the bottom of the question (sorry, this is as minimal as I can think of). The problem is that it will run into an infinite recursion error:

  1. To evaluate test() + test(), the compile looks at all potential overloads.
  2. The operator defined here is in fact a potential overload, so it tries to construct the return type.
  3. The return type has an enable_if clause, which is supposed to prevent it from being a valid overload, but the compiler just ignores that and tries to compute the decltype first, which requires ...
  4. ... an instantiation of operator+(test, test).

And we're back where we started. GCC is nice enough to spit an error; Clang just segfaults.

What would be a good, clean solution for this? (Keep in mind that there are also 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 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;
}

Here's rather clunky solution that I would rather not use unless I have 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);
Was it helpful?

Solution

As an addition to the comment of TemplateRex, I would suggest use 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;
}

OTHER TIPS

This is something that is touched upon on the boost page for enable_if, in an unerringly similar situation (though the error they wish to avoid is different). The solution of boost was to create a lazy_enable_if class.

The problem, as it is, is that the compiler will attempt to instantiate all the types present in the function signature, and thus the decltype(...) expression too. There is also no guarantee that the condition is computed before the type.

Unfortunately I could not come up with a solution to this issue; my latest attempt can be seen here and still triggers the maximum instantiation depth issue.

The most straightforward way to write mixed-mode arithmetic is to follow Scott Meyers's Item 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 would still need to write operator+= in order to provide a consistent interface ("do as the ints do"). If you also want to avoid that boilerplate, take a look at 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);
}

In either case, you can then 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. Live example. The Boost approach is very general, e.g. you can derive from boost::arithmetic to also provide operator* from your definition of operator*=.

NOTE: this code relies on implicit conversions of T to wrapper<T>. But to quote Scott Meyers:

Classes supporting implicit type conversions are generally a bad idea. Of course, there are exceptions to this rule, and one of the most common is when creating numerical types.

I have a better answer for your purpose: don't make it to complicated, don't use to much meta programming. Instead use simple function to unwrap and use normal expressions. You don't need to use enable_if to remove to operators from function overload set. If they are not used the will never need to compile and if the are used they give a meaningfull 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;
}

Edit:

As an intersting additional information I have to say, the orignal soultion from fzlogic compiles under msvc 11 (but not 10). Now my solution (presented here) doesn't compile on both (gives C1045). If you need to address these isues with msvc and gcc (I don't have clang here) you have to move logic to different namespaces and functions to prevent the compiler to uses ADL and take the templated operator+ into consideration.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top