Is it possible to write a generic function that can compare objects of different classes which do not define operator==
, something like:
template<typename T>
bool Compare(T obj1, T obj2) {
...?...
}
class Class1 {
int i;
int *pi;
};
class Class2 {
char c;
char *pc;
std::string *ps;
};
Class1 a1, b1;
Initialise...
bool c1 = Compare(a1, b1);
Class2 a2, b2;
Initialise...
bool c2 = Compare(a2, b2);
Where the values pointed to are also compared, i.e.:
*a1.pi == *b1.pi
If I compared the memory blocks of the two objects, I would get something like:
a1.pi == b1.pi
Well, first things first.
However, it's an occasion to come up with some fun templates, so I couldn't resist.
C++ has no reflection
Therefore, we cannot do a.GetFields()
and loop over it, as we would in C# or Java.
However, we still need a way to get fields and "enumerate" them somehow. Since C++ is strongly typed, we also need field types and can't rely on some "runtime magic" as we would in Javascript.
That means we need a type list. Many implementations exist, but we just need basic functionalities so here is the dumbest possible version :
#pragma once
#include <cstdint>
template <typename... Ts>
struct type_list;
namespace internal {
template <size_t i, typename T, typename... Ts>
struct type_at {
static_assert(i < sizeof...(Ts) + 1, "index out of range");
using type = typename type_at<i - 1, Ts...>::type;
};
template <typename T, typename... Ts> struct type_at<0, T, Ts...> {
using type = T;
};
template <size_t i, typename... Ts>
using type_at_t = typename type_at<i, Ts...>::type;
}
template <typename... Ts>
struct type_list {
static constexpr size_t length = sizeof...(Ts);
template <size_t index>
using at = internal::type_at_t<index, Ts...>;
};
template <>
struct type_list<> {
static constexpr size_t length = 0;
};
Then, we need a way to get fields in our "comparable classes". That means our classes will get a function which returns ith field. But, again, C++ is strongly typed so we can't have auto get_field(int x)
since return type is different for different indices.
Let's wrap everything in opaque pointers (void*) :
template<typename FieldType>
union opaque_field {
FieldType x;
void* opaque;
};
and start with a simple class:
class A {
friend struct GetFields<A>; // only addition to make in your business code
private:
int _x;
double _y;
A* _next;
public:
A(int x, int y, A* next = nullptr) {
this->_x = x;
this->_y = y;
this->_next = next;
}
};
And let's implement a fake reflection :
template<typename T>
struct GetFields;
template<typename T>
struct FieldTypes;
template<>
struct FieldTypes<A> {
using type = type_list<int, double, A*>;
};
template<>
struct GetFields<A> {
static void* get_field(const A& a, size_t index) {
switch (index) {
case 0:
return opaque_field<int> { a._x }.opaque;
case 1:
return opaque_field<double> { a._y }.opaque;
case 2:
return opaque_field<A*> { a._next }.opaque;
default:
return nullptr;
// do something (throw ?)
}
}
};
this ugly logic will have to be implemented for all "comparable" types (not scalar / pointers / arithmetic though). Again, why not just write a good old operator==, but let's continue having fun with c++.
Template comparison Once done, we can't implement a template compare function with following properties :
template<typename T1, typename T2, typename E = void>
struct compare_helper {
static bool compare(const T1& x, const T2& y) {
return false;
}
};
template<typename T>
struct compare_helper<T, T, std::enable_if_t<
(std::is_enum_v<T> || std::is_arithmetic_v<T>) && !std::is_pointer_v<T>>> {
static bool compare(const T& x, const T& y) {
return x == y;
}
};
template<typename T>
struct compare_helper<T*, T*> {
static bool compare(T* x, T* y) {
if (x == nullptr && y == nullptr) {
return true;
}
if ((x == nullptr && y != nullptr) || (y == nullptr && x != nullptr)) {
return false;
}
return compare_helper<T, T>::compare(*x, *y);
}
};
template<typename T>
struct compare_helper<T, T, std::enable_if_t<
(!std::is_pointer_v<T> && !std::is_enum_v<T> && !std::is_arithmetic_v<T>)
>> {
// oh the beautiful compile time loop because we can't do a for loop
template<size_t index, size_t stop>
struct inner {
using field_type = typename FieldTypes<T>::type::template at<index>;
static bool func(const T& x, const T& y) {
field_type left = opaque_field<field_type>{ .opaque = GetFields<A>::get_field(x, index) }.x;
field_type right = opaque_field<field_type>{ .opaque = GetFields<A>::get_field(y, index) }.x;
return compare_helper<field_type, field_type>::compare(left, right) && inner<index + 1, stop>::func(x, y);
}
};
template<size_t stop>
struct inner<stop, stop> {
static bool func(const T& x, const T& y) {
return true;
}
};
static bool compare(const T& x, const T& y) {
return inner<0, FieldTypes<T>::type::length>::func(x, y);
}
};
// final wrap in a template function for ease of use
template<typename T1, typename T2>
bool Compare(const T1& x, const T2& y) {
return compare_helper<T1, T2>::compare(x, y);
}
Then, it's quite straightforward to use :
int main() {
A* a = new A(1, 2);
A* b = new A(1, 2, a);
A* c = new A(1, 2, a);
printf("%d\n", Compare(a, b));
printf("%d\n", Compare(b, c));
return 0;
}
Complete example here.