14

This example :

#include <iostream>
#include <cstring>

struct A
{
    int  a;
    bool b;
};

bool foo( const A a1, const A a2 )
{
    return ( 0 == std::memcmp( &a1, &a2, sizeof( A ) ) );
}

int main()
{
    A a1 = A();
    a1.a = 5;a1.b = true;
    A a2 = A();
    a2.a = 5;a2.b = true;

    std::cout<<std::boolalpha << foo( a1, a2 ) << std::endl;
}

is going to produce false, because of padding.

I do not have access to the foo function, and I can not change the way the comparison is done.

Assuming a bool occupies 1 byte (that is true on my system), if I change the struct A to this :

struct A
{
  int a;
  bool b;
  char dummy[3];
};

then it works fine on my system (the output is true).

Is there anything else I could do to fix the above problem (get the true output)?

Jonas
  • 121,568
  • 97
  • 310
  • 388
BЈовић
  • 62,405
  • 41
  • 173
  • 273
  • Why are you passing by `const` value and not by `const&` ? ==> `foo( const A a1, const A a2 );` – iammilind Jun 30 '11 at 08:33
  • @iammilind The `foo` function is from a 3rd party library, and I do not have access to change it's signature – BЈовић Jun 30 '11 at 09:48
  • Have you tried packing the structure since you seem to have control over it? This way you might not have to use the memset "trick". VS: `#pragma pack(1)` right before the structure. G++ same. http://msdn.microsoft.com/en-us/library/2e70t5y1%28v=vs.80%29.aspx http://gcc.gnu.org/onlinedocs/gcc/Structure_002dPacking-Pragmas.html – RedX Jun 30 '11 at 10:06
  • @RedX I haven't, but I will try. Thanks for links – BЈовић Jun 30 '11 at 10:16

3 Answers3

17

The first one is not working because of padding in the struct. The padding is having different bit patterns for both objects.

If you use memset to set all the bits in the object before using it, then it will work:

A a1;
std::memset(&a1, 0, sizeof(A));
a1.a = 5;a1.b = true;

A a2;
std::memset(&a2, 0, sizeof(A));
a2.a = 5;a2.b = true;

Online demos:


By the way, you can write operator<, operator== etc, for PODs also.

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • `A a1 = A();` is same as `A a1; std::memset(&a1, 0, sizeof(A));`. I can write operators for POD, but I do not have access to the function doing the comparison, therefore I am stuck with std::memcmp – BЈовић Jun 30 '11 at 08:19
  • 2
    @VJo: NO. Its not same `A a1=A()` initializes only the members, not the padding. That is, `A a1=A()` is equivalent to `A a1; memset(&a1.a, 0, sizeof(a1.a)); memset(&a1.b, 0, sizeof(a1.b));`. The remaining `(sizeof(A) - sizeof(a1.a) - sizeof(a1.b))` bytes remains as such, containing garbage. – Nawaz Jun 30 '11 at 08:21
  • 1
    Well, strictly saying, `A a1=A()` ***is not guaranteed*** to initialize padding, but it still can do so. – sharptooth Jun 30 '11 at 08:33
6

Since C++11 we can use tuples for simple POD comparison (tuples use lexicographical comparison for >, <, >= and <= operators, more info on that: https://en.cppreference.com/w/cpp/utility/tuple/operator_cmp ) :

#include <iostream>
#include <tuple>

struct Point {
    int x;
    int y;
    int z;    
};


auto pointToTuple(const Point& p) {
    return std::make_tuple(p.x, p.y, p.z);
}

bool operator==(const Point& lhs, const Point& rhs ) {
    return pointToTuple(lhs) == pointToTuple(rhs);
}

bool operator<(const Point& lhs, const Point& rhs ) {
    return pointToTuple(lhs) < pointToTuple(rhs);
}

int main()
{

    Point a{1, 2, 3};
    Point b{1, 2, 3};
    Point c{2, 2, 2};

    std::cout << (pointToTuple(a) == pointToTuple(b) ? "true" : "false") << "\n"; //true
    std::cout << (pointToTuple(a) == pointToTuple(c) ? "true" : "false") << "\n"; //false

    std::cout << (a == b ? "true" : "false") << "\n"; //true
    std::cout << (a == c ? "true" : "false") << "\n"; //false

    std::cout << (a < b ? "true" : "false") << "\n"; //false
    std::cout << (a < c ? "true" : "false") << "\n"; //true

}

C++20 should bring us default comparisons (https://en.cppreference.com/w/cpp/language/default_comparisons). So if class defines operator<=> as defaulted, compiler will automatically generate ==, !=, <, <=, > and >= operators and code for them:

struct Point {
    int x;
    int y;
    int z;    

    auto operator<=>(const Point&) const = default;
};
Denis Sablukov
  • 3,360
  • 2
  • 26
  • 31
1

In C++14 and above you can use this library: https://github.com/apolukhin/magic_get/ to extract member types of POD. Than you can write generic comparison operator, which does not require memsetting memory of original object to erase padding like this:

#include "boost/pfr/precise.hpp"
template<typename T>
void foo(const T& a, const T& b)
{
    return boost::pfr::flat_less<T>{}(a, b);
}

This method has the advantage of not modifying code that creates objects (which can be valuable when it's not under your control), but it also generates additional binary code, and compilation with PFR library will be slower.

Still - it's most flexible and clean, since simple memcmp does not give you real semantic power (for example when you use custom comparison operators on subtypes of your PODs).


PS: using PFR library you can do multiple other things with PODs, such as printing them, iterating over members etc. Check more examples here:

http://apolukhin.github.io/magic_get/boost_precise_and_flat_reflectio/short_examples_for_the_impatient.html

Michał Łoś
  • 633
  • 4
  • 15
  • Didn't know that such lib exist) It's really good that you've mentioned it. I've noticed that this lib allows you compare and print structs. Could you uptate your answer with examples (like these http://apolukhin.github.io/magic_get/boost_precise_and_flat_reflectio/short_examples_for_the_impatient.html) in order to make your answer really great? – Denis Sablukov Feb 06 '19 at 20:01
  • @DenisSablukov: I will add link to examples, since this is out of scope of this question, but sure ; ). BTW - I've just noticed that it doesn't require C++17, 14 is enough. – Michał Łoś Feb 07 '19 at 09:40