No, memcmp
is not suitable to do this. And reflection in C++ is insufficient to do this at this point (there are going to be experimental compilers that support reflection strong enough to do this already, and c++23 might have the features you need).
Without built-in reflection, the easiest way to solve your problem is to do some manual reflection.
Take this:
struct some_struct {
int x;
double d1, d2;
char c;
};
we want to do the minimal amount of work so we can compare two of these.
If we have:
auto as_tie(some_struct const& s){
return std::tie( s.x, s.d1, s.d2, s.c );
}
or
auto as_tie(some_struct const& s)
-> decltype(std::tie( s.x, s.d1, s.d2, s.c ))
{
return std::tie( s.x, s.d1, s.d2, s.c );
}
for c++11, then:
template<class S>
bool are_equal( S const& lhs, S const& rhs ) {
return as_tie(lhs) == as_tie(rhs);
}
does a pretty decent job.
We can extend this process to be recursive with a bit of work; instead of comparing ties, compare each element wrapped in a template, and that template's operator==
recursively applies this rule (wrapping the element in as_tie
to compare) unless the element already has a working ==
, and handles arrays.
This will require a bit of a library (100ish lines of code?) together with writing a bit of manual per-member "reflection" data. If the number of structs you have is limited, it might be easier to write per-struct code manually.
There are probably ways to get
REFLECT( some_struct, x, d1, d2, c )
to generate the as_tie
structure using horrible macros. But as_tie
is simple enough. In c++11 the repetition is annoying; this is useful:
#define RETURNS(...) \
noexcept(noexcept(__VA_ARGS__)) \
-> decltype(__VA_ARGS__) \
{ return __VA_ARGS__; }
in this situation and many others. With RETURNS
, writing as_tie
is:
auto as_tie(some_struct const& s)
RETURNS( std::tie( s.x, s.d1, s.d2, s.c ) )
removing the repetition.
Here is a stab at making it recursive:
template<class T,
typename std::enable_if< !std::is_class<T>{}, bool>::type = true
>
auto refl_tie( T const& t )
RETURNS(std::tie(t))
template<class...Ts,
typename std::enable_if< (sizeof...(Ts) > 1), bool>::type = true
>
auto refl_tie( Ts const&... ts )
RETURNS(std::make_tuple(refl_tie(ts)...))
template<class T, std::size_t N>
auto refl_tie( T const(&t)[N] ) {
// lots of work in C++11 to support this case, todo.
// in C++17 I could just make a tie of each of the N elements of the array?
// in C++11 I might write a custom struct that supports an array
// reference/pointer of fixed size and implements =, ==, !=, <, etc.
}
struct foo {
int x;
};
struct bar {
foo f1, f2;
};
auto refl_tie( foo const& s )
RETURNS( refl_tie( s.x ) )
auto refl_tie( bar const& s )
RETURNS( refl_tie( s.f1, s.f2 ) )
c++17 refl_tie(array) (fully recursive, even supports arrays-of-arrays):
template<class T, std::size_t N, std::size_t...Is>
auto array_refl( T const(&t)[N], std::index_sequence<Is...> )
RETURNS( std::array<decltype( refl_tie(t[0]) ), N>{ refl_tie( t[Is] )... } )
template<class T, std::size_t N>
auto refl_tie( T(&t)[N] )
RETURNS( array_refl( t, std::make_index_sequence<N>{} ) )
Live example.
Here I use a std::array
of refl_tie
. This is much faster than my previous tuple of refl_tie at compile time.
Also
template<class T,
typename std::enable_if< !std::is_class<T>{}, bool>::type = true
>
auto refl_tie( T const& t )
RETURNS(std::cref(t))
using std::cref
here instead of std::tie
could save on compile-time overhead, as cref
is a much simpler class than tuple
.
Finally, you should add
template<class T, std::size_t N, class...Ts>
auto refl_tie( T(&t)[N], Ts&&... ) = delete;
which will prevent array members from decaying to pointers and falling back on pointer-equality (which you probably don't want from arrays).
Without this, if you pass an array to a non-reflected struct in, it falls back on pointer-to-non-reflected struct refl_tie
, which works and returns nonsense.
With this, you end up with a compile-time error.
Support for recursion through library types is tricky. You could std::tie
them:
template<class T, class A>
auto refl_tie( std::vector<T, A> const& v )
RETURNS( std::tie(v) )
but that doesn't support recursion through it.
struct { some_3byte_sized_type x; int y;}
will most likely have padding to aligny
, but isnt this the same for two instances? Sorry for hijacking the discussion, maybe I should open a seperate quesiton – Kmmemcmp
you include those padding bits in your comparison. – Ascetic==
operator. Usingmemcmp
is unreliable, and sooner or later you'll be dealing with some class that has to "do it a little differently from the others." It's very clean and efficient to implement that in an operator. The actual behavior will be polymorphic but the source code will be clean ... and, obvious. – Fallow==
ness. – Danillestd::uint8_t*
is pointless since the cast tovoid*
is implicit. The type also isn't useful, since it really should bevoid*
, notuint8_t
. And C-style casts are dangerous in general. – Oldfangled