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:
test() + test()
, the compile looks at all potential overloads.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 ...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);
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;
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With