The original working code
I have a class template with two template parameters and an optimized operator== when the two types are the same and one another condition is satisfied. My original code is as follows (for demonstration purposes I make the generic comparison return false and the one where T1 == T2 return true):
template<typename T1, typename T2>
struct my_class
{
// A few hundred LOCs
};
template<typename U1, typename U2>
bool operator==(my_class<U1, U2> const& lhs, my_class<U1, U2> const& rhs)
{
return false;
}
template<typename U>
auto operator==(my_class<U, U> const& lhs, my_class<U, U> const& rhs)
-> std::enable_if_t<some_condition, bool>
{
return true;
}
The idea is that the first overload of operator== is the default, and when both U1 and U2 are the same type and some_condition is satisfied the second overload is valid and picked as a better match.
The problem
I recently started implementing more and more operators in my generic libraries as hidden friends to avoid some unwanted implicit conversions and to reduce the overload set the compiler has to choose from at namespace scope.
I first tried the most obvious approach to friends, which is to move the definitions as is in the class template and to prefix them with friend:
template<typename T1, typename T2>
struct my_class
{
// A few hundred LOCs
template<typename U1, typename U2>
friend bool operator==(my_class<U1, U2> const& lhs, my_class<U1, U2> const& rhs)
{
return false;
}
template<typename U>
friend auto operator==(my_class<U, U> const& lhs, my_class<U, U> const& rhs)
-> std::enable_if_t<some_condition, bool>
{
return true;
}
};
Much not to my surprise this didn't work and I got redefinition errors. The reason why is explained - along with a standard quote - in this answer.
An attempt
We are always comparing my_class with matching template parameters, so I figured that I could get rid of the inner template<typename U1, typename U2> in the first definition, however it is trickier in the second one since the single template parameter was used to create a more specialized overload of operator==. Another solution would be to put that overload in a specialization of my_class<T, T> but since the class is big I didn't feel like duplicating its contents since pretty much everything else is the same. I could probably introduce another layer of indirection for the common code but I already have a hefty amount of indirection.
Failing that I tried to fallback to good old SFINAE to make sure that T1 and T2 are the same:
template<typename T1, typename T2>
struct my_class
{
// A few hundred LOCs
friend auto operator==(my_class const& lhs, my_class const& rhs)
-> bool
{
return false;
}
friend auto operator==(my_class const& lhs, my_class const& rhs)
-> std::enable_if_t<std::is_same<T1, T2>::value && some_condition, bool>
{
return true;
}
};
For some reason I don't claim to fully understand the second operator== above is actually ill-formed, but we can get around that by adding back some additional defaulted template parameters into the mix:
template<typename T1, typename T2>
struct my_class
{
// A few hundred LOCs
friend auto operator==(my_class const& lhs, my_class const& rhs)
-> bool
{
return false;
}
template<typename U1=T1, typename U2=T2>
friend auto operator==(my_class const& lhs, my_class const& rhs)
-> std::enable_if_t<std::is_same<U1, U2>::value && some_condition, bool>
{
return true;
}
};
This compiles as expected, but comparing two instances of my_class with matching T1 and T2 now returns false because the second overload of operator== is less specialized than the first one. An educated guess tells me that the new template layer is the reason, so I added back template parameters to the first overload of operator== and also an SFINAE condition which is a negation of the other one to make sure that the overload wouldn't be ambiguous with matching T1 and T2:
template<typename T1, typename T2>
struct my_class
{
// A few hundred LOCs
template<typename U1=T1, typename U2=T2>
friend auto operator==(my_class const& lhs, my_class const& rhs)
-> std::enable_if_t<not(std::is_same<U1, U2>::value && some_condition), bool>
{
return false;
}
template<typename U1=T1, typename U2=T2>
friend auto operator==(my_class const& lhs, my_class const& rhs)
-> std::enable_if_t<std::is_same<U1, U2>::value && some_condition, bool>
{
return true;
}
};
This finally gives the intended result while also providing the benefits of hidden friends, but the cost is a bit high in term of readability and maintainability.
Back to the question I originally meant to ask
I tried to explain my problem and rough solution and how I got to it above. My question is: is there a better way to achieve the same result (hidden friends with my code) without having to dive into all the template issues I highlighted above? Can I have such hidden friends while relying on the built-in partial ordering of function templates instead of replacing it with another layer of SFINAE as I did?
Would you accept this?
template<typename T1, typename T2>
struct my_class
{
friend bool operator==(my_class const& lhs, my_class const& rhs)
{
if constexpr (std::is_same_v<T1, T2>) {
return condition;
} else {
return false;
}
}
};
Related
I'm developing a header-only library for automatic/algorithmic differentiation. The goal is to be able to simply change the type of the variables being fed to a function and calculate first and second derivatives. For this, I've created a template class that allows the programmer to select the storage type for the private data members. Included is a snippet below with an offending operator overload.
template <typename storage_t>
class HyperDual
{
template <typename T> friend class HyperDual;
public:
template <typename T>
HyperDual<storage_t> operator+(const HyperDual<T>& rhs) const
{
HyperDual<storage_t> sum;
for (size_t i = 0; i < this->values.size(); i++)
sum.values[i] = this->values[i] + rhs.values[i];
return sum;
}
protected:
std::vector<storage_t> values;
};
Later on, to maximize the versatility, I provide template functions to allow interaction.
template <typename storage_t, typename T>
HyperDual<storage_t> operator+(const HyperDual<storage_t>& lhs, const T& rhs)
{
static_assert(std::is_arithmetic<T>::value && !(std::is_same<T, char>::value), "RHS must be numeric");
return HyperDual<storage_t>(lhs.values[0] + rhs);
}
template <typename storage_t, typename T>
HyperDual<storage_t> operator+(const T& lhs, const HyperDual<storage_t>& rhs)
{
static_assert(std::is_arithmetic<T>::value && !(std::is_same<T, char>::value), "LHS must be numeric");
return HyperDual<storage_t>(lhs + rhs.values[0]);
}
What I'm encountering is that the compiler is trying to instantiate the second non-member template function.
#include "hyperspace.h"
int main()
{
HyperDual<long double> one(1); // There is an appropriate constructor
HyperDual<double> two(2);
one + two;
return 0;
}
I get the static_assert generated error "LHS must be numeric" for this. How would I resolve the ambiguity?
use enable_if_t to make the non-member template can only be applied in the specific context?
template <typename storage_t, typename T, typename = enable_if_t<std::is_arithmetic<T>::value && !(std::is_same<T, char>::value)>>
HyperDual<storage_t> operator+(const HyperDual<storage_t>& lhs, const T& rhs)
{
static_assert(std::is_arithmetic<T>::value && !(std::is_same<T, char>::value), "RHS must be numeric");
return HyperDual<storage_t>(lhs.values[0] + rhs);
}
the static_assert may be duplicated here.
Ok. I found my own issue. It comes down to the difference between static_assert and std::enable_if
Replacing my template declaration and removing static_assert, I achieve equivalent functionality:
template <typename storage_t, typename T,
typename = typename std::enable_if<std::is_arithmetic<T>::value && !std::is_same<T, char>::value>::type>
HyperDual<storage_t> operator+(const T& lhs, const HyperDual<storage_t>& rhs)
{
return HyperDual<storage_t>(lhs + rhs.value());
}
(Small detail, but rhs.values[0] was replaced with rhs.value(). This had nothing to do with the template issue, but was related to member access.
I want to create a shared_ptr content-comparison functor to stand in for std::less<T> in associative containers and std algorithms. I've seen several examples of custom comparators that use the following (or similar) model:
template <typename T>
struct SharedPtrContentsLess {
bool operator()(const boost::shared_ptr<T>& lhs,
const boost::shared_ptr<T> rhs) const {
return std::less<T>(*lhs, *rhs);
//or: return (*lhs) < (*rhs);
}
//defining these here instead of using std::binary_functor (C++11 deprecated)
typedef boost::shared_ptr<T> first_argument_type;
typedef boost::shared_ptr<T> second_argument_type;
typedef bool return_type;
};
But why wouldn't I want to instead extend std::less? Like so:
template <typename T>
struct SharedPtrContentsLess : public std::less< boost:shared_ptr<T> > {
bool operator()(const boost::shared_ptr<T>& lhs,
const boost::shared_ptr<T> rhs) const {
return std::less<T>(*lhs, *rhs);
}
};
Does this buy me anything at all?
I would think this gets me the typedefs for free, as though I was extending the deprecated std::binary_function. In C++03, I actually would be extending it through std::less. However, this would also be portable from C++03 to C++11/14 and even C++17 when std::binary_function will be removed, as it just follows the changes in std::less.
I've read a bunch of answers on StackOverflow regarding std::less use, custom comparison functors, and even some of the Standard specs and proposals. I see specializations of std::less and guidance not to extend STL containers, but I can't seem to find any examples of extending std::less or guidance against it. Am I missing an obvious reason not to do this?
EDIT: Removed C++11 tag, as it is causing confusion to the answerers. I am hoping to get forward-portability, but C++03 is required. If you provide a C++11-only answer for others to use (totally fine), please note that.
You can create a reusable template towards any dereferencable object (i.e. any (smart) pointer) by simply forwarding the call to std::less or any other comparable object.
// c++11
template<template<class> Op, class T> struct deref_mixin;
template<template<class> Op, class T>
struct deref_mixin {
auto operator()(const T &l, const T &r) const
-> decltype(std::declval<Op<T>>()(*l, *r)) {
return Op<T>{}(*l, *r);
}
};
template<template<class> Op>
struct deref_mixin<Op, void> {
template<class T, class U>
auto operator()(const T &l, const U &r) const
-> decltype(std::declval<Op<T>>()(*l, *r)) {
return Op<void>{}(*l, *r);
}
};
template<class T> using less_deref = deref_mixin<std::less, T>;
template<class T> using greater_deref = deref_mixin<std::greater, T>;
template<class T> using my_comparator_deref = deref_mixin<my_comparator, T>;
// c++03
template<template<class> Op, class T>
struct deref_mixin {
bool operator()(const T &l, const T &r) const {
Op<T> op;
return op(*l, *r);
}
};
// Technically, the void template partial specialization isn't defined in c++03, but it should have been :)
template<template<class> Op>
struct deref_mixin<Op, void> {
template<class T, class U>
bool operator()(const T &l, const U &r) const {
Op<void> op;
return op(*l, *r);
}
};
template<class T> struct less_deref : deref_mixin<std::less, T> {};
As you said in your question if you inherit from std::less the you would get the three typedefs that are in std::less. What I like most about inheriting from it is it describes your intent. When I see
struct some_non_specific_name : std::less<some_type>
I know right there that this is a functor that is going to behave as an < for some_type. I don't have to read the struct body to find out anything.
As far as I can see, you are not missing any disadvantage. As you mentioned, you would automatically get the typedefs. The operator< has to be defined in both cases, and there is no difference in its implementation.
There is one thing that you might get that you might find neat, bad or just not applicable to your usecase (from here and here): there is a specialization of std::less for std::less<void> that has a template operator< deduces the return type of the operator< for the given arguments.
Unless you intend to use a SharedPtrContentsLess<void> (which probably doesn't make sense at all), both solutions would be equivalent.
I'd write a deref_less. First, my_less that smartly calls std::less:
struct my_less {
template<class Lhs, class Rhs,
class R = std::result_of_t< std::less<>( Lhs const&, Rhs const& ) >
// class R = decltype( std::declval<Lhs const&>() < std::declval<Rhs const&>() )
>
R operator()(Lhs const&lhs, Rhs const&rhs)const{
return std::less<>{}(lhs, rhs); // or lhs<rhs
}
// exact same type uses `std::less<T>`:
template<class T,
class R = std::result_of_t< std::less<>( T const&, T const& ) >
>
R operator()(T const& lhs, T const& rhs)const{
return std::less<T>{}(lhs, rhs);
}
template<class Lhs, class Rhs,
std::enable_if_t< std::is_base_of<Lhs, Rhs>{} && !std::is_same<Lhs, Rhs>{} >* = nullptr
>
bool operator()(Lhs const* lhs, Rhs const* rhs)const{
return std::less<Lhs const*>{}(lhs, rhs);
}
template<class Lhs, class Rhs,
std::enable_if_t< std::is_base_of<Rhs, Lhs>{} && !std::is_same<Lhs, Rhs>{} >* = nullptr
>
bool operator()(Lhs const* lhs, Rhs const* rhs)const{
return std::less<Rhs const*>{}(lhs, rhs);
}
template<class Lhs, class Rhs,
std::enable_if_t<
!std::is_base_of<Rhs, Lhs>{}
&& !std::is_base_of<Lhs, Rhs>{}
&& !std::is_same<Lhs, Rhs>{}
>* = nullptr
>
bool operator()(Lhs const* lhs, Rhs const* rhs)const = delete;
};
then, a deref_less that does a * then calls myless:
struct deref_less {
template<class Lhs, class Rhs,
class R = std::result_of_t< my_less( decltype(*std::declval<Lhs>()), decltype(*std::declval<Rhs>()) ) >
>
R operator()(Lhs const& lhs, Rhs const&rhs)const {
return my_less{}( *lhs, *rhs );
}
};
in C++14, but everything I used is easy to replace (std::less<> can be replaced with decltype and <s for example).
Because std::less lacks a virtual destructor (i.e. implicit destructor only), inheriting from it could technically lead to undefined behavior. Since neither type contains any data members, destruction should work no matter how the object is referenced, but the standard forbids polymorphic deletion through static destructors because it presents a strong possibility of problems (slicing, incomplete deletion) in most cases.
See this answer:
Thou shalt not inherit from std::vector
With n different classes, which should all be comparable with operator== and operator!=, it would be necessary to implement (n ^ 2 - n) * 2 operators manually. (At least I think that's the term)
That would be 12 for three classes, 24 for four. I know that I can implement a lot of them in terms of other operators like so:
operator==(A,B); //implemented elsewhere
operator==(B,A){ return A == B; }
operator!=(A,B){ return !(A == B); }
but it still seems very tedious, especially because A == B will always yield the same result as B == A and there seems to be no reason whatsoever to implement two version of them.
Is there a way around this? Do I really have to implement A == B and B == A manually?
Use Boost.Operators, then you only need to implement one, and boost will define the rest of the boilerplate for you.
struct A
{};
struct B : private boost::equality_comparable<B, A>
{
};
bool operator==(B const&, A const&) {return true;}
This allows instances of A and B to be compared for equality/inequality in any order.
Live demo
Note: private inheritance works here because of the Barton–Nackman trick.
In the comments the problem is further explained by stating that all of the types are really different forms of smart pointers with some underlying type. Now this simplifies quite a lot the problem.
You can implement a generic template for the operation:
template <typename T, typename U>
bool operator==(T const & lhs, U const & rhs) {
return std::addressof(*lhs) == std::addressof(*rhs);
}
Now this is a bad catch all (or rather catch too many) implementation. But you can narrow down the scope of the operator by providing a trait is_smart_ptr that detects whether Ptr1 and Ptr2 are one of your smart pointers, and then use SFINAE to filter out:
template <typename T, typename U,
typename _ = typename std::enable_if<is_pointer_type<T>::value
&& is_pointer_type<U>::value>::type >
bool operator==(T const & lhs, U const & rhs) {
return std::addressof(*lhs) == std::addressof(*rhs);
}
The type trait itself can be just a list of specializations of a template:
template <typename T>
struct is_pointer_type : std::false_type {};
template <typename T>
struct is_pointer_type<T*> : std::true_type {};
template <typename T>
struct is_pointer_type<MySmartPointer<T>> : std::true_type {};
template <typename T>
struct is_pointer_type<AnotherPointer<T>> : std::true_type {};
It probably makes sense not to list all of the types that match the concept of pointer, but rather test for the concept, like:
template <typename T, typename U,
typename _ = decltype(*declval<T>())>
bool operator==(T const & lhs, U const & rhs) {
return std::addressof(*lhs) == std::addressof(*rhs);
}
Where the concept being tested is that it has operator* exists. You could extend the SFINAE check to verify that the stored pointer types are comparable (i.e. that std::addressof(*lhs) and std::addressof(*rhs) has a valid equality:
template <typename T, typename U,
typename _ = decltype(*declval<T>())>
auto operator==(T const & lhs, U const & rhs)
-> decltype(std::addressof(*lhs) == std::addressof(*rhs))
{
return std::addressof(*lhs) == std::addressof(*rhs);
}
And this is probably as far as you can really get: You can compare anything that looks like a pointer to two possibly unrelated objects, if raw pointers to those types are comparable. You might need to single out the case where both arguments are raw pointers to avoid this entering out into a recursive requirement...
not necesarily:
template<class A, class B>
bool operator==(const A& a, const B& b)
{ return b==a; }
works for whatever A and B there is a B==A implementation (otherwise will recourse infinitely)
You can also use CRTP if you don't want the templetized == to work for everything:
template<class Derived>
class comparable {};
class A: public comparable<A>
{ ... };
class B: public comparable<B>
{ ... };
bool operator==(const A& a, const B& b)
{ /* direct */ }
// this define all reverses
template<class T, class U>
bool operator==(const comparable<T>& sa, const comparable<U>& sb)
{ return static_cast<const U&>(sb) == static_cast<const T&>(sa); }
//this defines inequality
template<class T, class U>
bool operator!=(const comparable<T>& sa, const comparable<U>& sb)
{ return !(static_cast<const T&>(sa) == static_cast<const U&>(sb)); }
Using return type SFINAE yo ucan even do something like
template<class A, class B>
auto operator==(const A& a, const B& b) -> decltype(b==a)
{ return b==a; }
template<class A, class B>
auto operator!=(const A& a, const B& b) -> decltype(!(a==b))
{ return !(a==b); }
The goal here is to deal with large n reasonably efficiently.
We create an order, and forward all comparison operators to comp after reordering them to obey that order.
To do this, I start with some metaprogramming boilerplate:
template<class...>struct types{using type=types;};
template<class T,class types>struct index_of{};
template<class T,class...Ts>struct index_of<T,types<T,Ts...>>:
std::integral_constant<unsigned,0>
{};
template<class T,class U,class...Us>struct index_of<T,types<U,Us...>>:
std::integral_constant<unsigned,1+index_of<T,types<Us...>>::value>
{};
which lets us talk about ordered lists of types. Next we use this to impose an order on these types:
template<class T, class U,class types>
struct before:
std::integral_constant<bool, (index_of<T,types>::value<index_of<U,types>::value)>
{};
Now we make some toy types and a list:
struct A{}; struct B{}; struct C{};
typedef types<A,B,C> supp;
int comp(A,B);
int comp(A,C);
int comp(B,C);
int comp(A,A);
int comp(B,B);
int comp(C,C);
template<class T,class U>
std::enable_if_t<before<T,U,supp>::value, bool>
operator==(T const& t, U const& u) {
return comp(t,u)==0;
}
template<class T,class U>
std::enable_if_t<!before<T,U, supp>::value, bool>
operator==(T const& t, U const& u) {
return comp(u,t)==0;
}
template<class T,class U>
std::enable_if_t<before<T,U,supp>::value, bool>
operator<(T const& t, U const& u) {
return comp(t,u)<0;
}
template<class T,class U>
std::enable_if_t<!before<T,U, supp>::value, bool>
operator<(T const& t, U const& u) {
return comp(u,t)>0;
}
etc.
The basic idea is that supp lists the types you want to support, and their prefered order.
Boilerplate operators then forward everything to comp.
You need to implement n*(n-1)/2 comps to handles each pair, but only in one order.
Now for the bad news: probably you want to lift each type to some common type and compare there, rather than het lost in the combinatorial morass.
Suppose you can define a type Q which can store the imformation required to sort any of them.
Then write convert-to-Q code from each type, and implement comparison on Q. This reduces the code written to O(a+b), where a is the number of types and b the number of operators supported.
As an example, smart pointers can be pointer-ordered between each other this way.
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:
To evaluate test() + test(), the compile looks at all potential overloads.
The operator defined here is in fact a potential overload, so it tries to construct the return type.
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 ...
... 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);
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.
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.
What is preferable (if any)?
Variant A (Barton-Nackman):
template<class T>
struct equal_comparable {
friend bool operator == (const T & t1, const T & t2) {
return t1.equalTo (t2);
}
};
class MyClass : private equal_comparable<MyClass> {
bool equalTo (const MyClass & other) //...
};
Variant B (std::enable_if):
struct MyClass {
static const bool use_my_equal = true;
bool equalTo (const MyClass & other) //...
};
template<class T>
typename std::enable_if<
T::use_my_equal,
bool
>::type
operator == (const T & t1, const T & t2) { return t1.equalTo (t2); }
I'd prefer to use Boost.Operators mentioned by #SteveJessop in the comments, which formalizes and automates your first approach. They also take care of the empty base optimization if you happen to need multiple sets of operators (and hence would need multiple inheritance). It's not so much the savings in typing, but also the code documentation/enforcement value, since these bases classes are right at the front of the class interface. In that sense, it's a primitive way of Concepts.