Do I have to implement commutativity for comparison operators manually? - c++

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.

Related

How can I tell the compiler that MyCustomType is equality_comparable_with SomeOtherType?

Suppose I have a MyCustomType that has comparisons with SomeOtherType:
struct SomeOtherType {
int value;
constexpr bool operator==(const SomeOtherType& rhs) const = default;
};
struct MyCustomType {
int x;
constexpr bool operator==(const MyCustomType& rhs) const = default;
constexpr bool operator==(const SomeOtherType& rhs) const {
return x == rhs.value;
}
friend constexpr bool operator==(const SomeOtherType& lhs, const MyCustomType& rhs) {
return lhs.value == rhs.x;
}
};
This is great, but static_assert(std::equality_comparable_with<MyCustomType, SomeOtherType>); fails, meaning that I can't use them for heterogenous lookup in the std::ranges algorithms:
error: no type named 'type' in 'struct std::common_reference<const MyCustomType&, const SomeOtherType&>'
I understand why this fails: we may have an operator==, but we don't meet the common-reference requirements (see also Does `equality_­comparable_with` need to require `common_reference`? ). However, my type is, practically speaking, equality comparable with SomeOtherType. How can I convince the compiler that this is the case?
Philosophically, the common-reference requirements of std::equality_comparable_with are explicitly encoding the implicit statement made when writing a heterogenous operator==(T, U) to actually mean equality*: there is some common supertype "T union U" for which the operator== is equality. This "T union U" doesn't actually exist in the code for MyCustomType and SomeOtherType. If we make the type actually exist by specializing std::common_reference_t, then we can meet std::equality_comparable_with.
*Some types use operator== for equivalence rather than equality (e.g. iterators+sentinels), and as such should not and do not meet std::equality_comparable_with.
We can use the std::basic_common_reference customization point to specify a proxy reference:
The class template basic_common_reference is a customization point that allows users to influence the result of common_reference for user-defined types (typically proxy references).
For this to work, we need:
eq_proxy_ref<T> which acts like a reference for T.
MyCustomType must be implicitly convertible to eq_proxy_ref<T>.
SomeOtherType must be implicitly convertible to eq_proxy_ref<T>.
basic_common_reference of MyCustomType and SomeOtherType must return this eq_proxy_ref<int>. A eq_proxy_ref<MyCustomProxy> could work too if you wanted to avoid leaking the internals of MyCustomType.
eq_proxy_ref<T> must have comparison operators between itself.
eq_proxy_ref<T> must obey the spirit of the requirement.
Beware, however, that std::common_reference_t is used for more than just equality, including std::three_way_comparable_with, std::totally_ordered_with, and some of the Ranges algorithms or views. As such, your eq_proxy_ref<T> should actually be a common reference for your two types, not just a mechanism to enable equality.
An example meeting these constraints follows:
#include <concepts>
#include <type_traits>
// Assuming you don't own SomeOtherType:
template <typename T>
class MyCustomTypeEqProxy {
template <typename>
friend class MyCustomTypeEqProxy;
private:
T ref_;
public:
template <typename U>
requires std::convertible_to<U, T>
constexpr MyCustomTypeEqProxy(U ref)
: ref_(ref)
{}
constexpr MyCustomTypeEqProxy(const SomeOtherType& rhs)
requires std::convertible_to<const int&, T>
: ref_(rhs.value)
{}
template <typename U>
requires std::equality_comparable_with<T, U>
constexpr bool operator==(const MyCustomTypeEqProxy<U>& rhs) const {
return ref_ == rhs.ref_;
};
};
struct MyCustomType {
int x;
constexpr bool operator==(const MyCustomType& rhs) const = default;
constexpr bool operator==(const SomeOtherType& rhs) const {
return x == rhs.value;
}
friend constexpr bool operator==(const SomeOtherType& lhs, const MyCustomType& rhs) {
return lhs.value == rhs.x;
}
constexpr operator MyCustomTypeEqProxy<int>() const { return MyCustomTypeEqProxy<int>(x); }
};
namespace std {
// May not be needed, but allows the custom proxy reference to expand to common references
// of what we're comparing against.
template <typename T, typename U, template <typename> class TQ, template <typename> class UQ>
struct basic_common_reference<::MyCustomTypeEqProxy<T>, U, TQ, UQ> {
using type = ::MyCustomTypeEqProxy< std::common_reference_t<T, UQ<U>> >;
};
template <typename T, typename U, template <typename> class TQ, template <typename> class UQ>
struct basic_common_reference<T, ::MyCustomTypeEqProxy<U>, TQ, UQ> {
using type = ::MyCustomTypeEqProxy< std::common_reference_t<TQ<T>, U> >;
};
// Tell std::common_reference_t about MyCustomTypeEqProxy
template <template <typename> class LQ, template <typename> class RQ>
struct basic_common_reference<::MyCustomType, ::SomeOtherType, LQ, RQ> {
using type = ::MyCustomTypeEqProxy<int>;
};
template <template <typename> class LQ, template <typename> class RQ>
struct basic_common_reference<::SomeOtherType, ::MyCustomType, LQ, RQ> {
using type = ::MyCustomTypeEqProxy<int>;
};
}
Compiler Explorer link
I suspect that I missed some nuances, but this is enough to meet std::equality_comparable_with.

Hidden friend templates in class template

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

Should I extend std::less for a comparison functor?

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

Barton-Nackman vs std::enable_if

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.

custom smart pointers and polynomial number of comparison operators

Lets say I want to implement a smart pointer a_ptr which can be compared with other smart pointers.
Then I need to implement all permutations of the comparison operators:
template<class T, class U>
bool operator==(const a_ptr<T>& a, const a_ptr<U>& b)
{
return a.get() == b.get();
}
template<class T, class U>
bool operator==(const std::shared_ptr<T>& a, const a_ptr<U>& b)
{
return a.get() == b.get();
}
template<class T, class U>
bool operator==(const a_ptr<T>& a, const std::shared_ptr<U>& b)
{
return a.get() == b.get();
}
and etc... for the rest of the operators.
Then maybe I would like to implement another smart pointer b_ptr, which would give me 6 versions for every comparison operator (since I want it to also work with a_ptr), clearly not manageable.
Is there any way to get around this problem?
EDIT:
I should have probably mentioned that I want to create wrappers around smart pointers, in which case this question makes more sense, e.g.
template<typename T>
class a_ptr
{
public:
const T* get() const {return p_.get();}
private:
std::shared_ptr<T> p_;
};
template<typename T>
class b_ptr
{
public:
const T* get() const {return p_.get();}
private:
a_ptr<T> p_;
};
If any of these, except the one comparing two a_ptr, ever hold true, your program has a bug. So, just drop it. Smart pointer is smart, because it's responsible of managing the memory behind it. And you just cannot have two different smart pointers managing one piece of memory.
Consider unique_ptr and shared_ptr. One destroys the pointer as soon as the owning smart pointer is destroyed. Second destroys the pointer only when all owning smart pointers are destroyed. I think it's fairly obvious it would quickly lead to double deletes and other fun stuff.
Ben Voigt is on the right track -- except of course we don't have concepts.
template<
typename Lhs
, typename Rhs
, typename = typename std::enable_if<
/* magic */
>::type
>
bool
operator==(Lhs const& lhs, Rhs const& rhs)
{
return lhs.get() == rhs.get();
}
This must be in the same namespace of a_ptr and b_ptr for ADL to kick (so if they are in separate namespaces you need one version of each operator for each namespace).
There are several possibilities for the magic trait to work. The one that is conceptually the simplest is to have a trait that is specialized for each pointer type you care about:
template<typename T>
struct is_smart_pointer: std::false_type {};
template<typename T, typename D>
struct is_smart_pointer<std::unique_ptr<T, D>>: std::true_type {};
// and so on...
A more elaborate trait would check that the type supports a get member. Actually, we don't need a trait for that!
template<typename Lhs, typename Rhs>
auto operator==(Lhs const& lhs, Rhs const& rhs)
-> decltype( lhs.get() == rhs.get() )
{
return lhs.get() == rhs.get();
}
This simple operator will be picked up by ADL, except that it will SFINAE itself out if one of the type doesn't support get, or if the comparison doesn't work.
How about:
template<typename T> struct you_need_a_pointer_to_do_that{} unwrap_ptr(const T&);
template<typename U> U* unwrap_ptr(U* p) { return p; }
template<typename U> U* unwrap_ptr(const a_ptr<U>& p) { return p.get(); }
template<typename U> U* unwrap_ptr(const unique_ptr<U>& p) { return p.get(); }
...
template<typename T, typename PU> auto operator<(const a_ptr<T>& a, const PU& b) -> decltype(unwrap_ptr(a) < unwrap_ptr(b)) { return unwrap_ptr(a) < unwrap_ptr(b); }
The decltype bit rejects non-pointers due to SFINAE. And the unwrap_ptr helper homogenizes the syntax for pointer access, with complexity only linear in the number of pointer types.
You can just tag your smart pointer types and then implement a generic version which relies on at least one of the passed arguments to be tagged:
template <typename T> struct is_my_smartpointer: std::false_type {};
template <typename T> struct is_my_smartpointer<a_ptr<T> >: std::true_type {};
template <typename S, typename T>
typename std::enable_if<is_my_smartpointer<S>::value || is_my_smartpointer<T>::value, bool>::type
operator== (S const& s, T const& t) {
return s.get() == t.get();
}
You could inherit your custom smart pointer from an existing smart pointer. This way you won't have to implement any operators at all.
Also as as Cat Plus Plus said. You cannot have two smart pointer managing one piece of memory. So If you're implementing a smart pointer you should make sure that holds true. You should make operator= private so it cannot be copied into another location.
It can be as difficult as writing a sfinae trait for smart pointers. As soon as you get it, the solution is pretty straight forward, you don't need c++11 for this:
#include <memory>
#include <type_traits>
#include <iostream>
namespace my {
template<class T>
class a_ptr
{
T* t_;
public:
typedef T element_type;
element_type* get() const throw() { return t_; }
};
template<class T> class is_smart_ptr; // sfinae test for T::get()?
template<class T, class U>
typename std::enable_if<
is_smart_ptr<T>::value &&
is_smart_ptr<U>::value,
bool
>::type
operator==(const T& a, const U& b)
{
return a.get() == b.get();
}
}
int main()
{
my::a_ptr<int> a;
std::shared_ptr<int> b;
a == b;
}
And since std::shared_ptr already have operator==, you need to put your comparison operator in the same namespace where your smart pointer type is.