overloading operator == for pods
Asked Answered
B

2

9

I am working on some low level code with high level interfaces and felt need for comparisons operator for unit testing for plain old data types(like FILETIME struct) but since C++ doesn't even provide memberwise comparisons, so I wrote this:

template <typename Type>
std::enable_if_t<std::is_pod<Type>::value, bool> operator==(const Type& a,
                                                            const Type& b) {
  return std::memcmp(&a, &b, sizeof(Type)) == 0;
}

So my question is, is this a good way or there are some hidden demons which will give me trouble later down the development cycle but it's kinda working for now.

Bluebeard answered 3/6, 2018 at 13:41 Comment(4)
This can fail for types with padding. Relevant: https://mcmap.net/q/554403/-define-generic-comparison-operator/3002139 and https://mcmap.net/q/346516/-do-the-padding-bytes-of-a-pod-type-get-copied/3002139Lettered
For the future: C++20 will provide comparisons for suitably simple types via the new <=> operator.Zircon
To add on @DavisHerring, it's called the 'spaceship operator'Marlyn
Heh. C++ is stealing features from Perl now? :-)Malcom
A
1

Is C++14 available? If so, consider PFR library, which makes structures into tuples

Appetizing answered 28/2, 2020 at 19:28 Comment(0)
G
0

This question is a restricted variant of Define generic comparison operator, as noted in the comments. An example of the dangers and effects of padding on the proposed operator== for POD is:

template <typename Type>
std::enable_if_t<std::is_pod<Type>::value, bool> operator==(const Type& a,
                                                            const Type& b) 
{
  return std::memcmp(&a, &b, sizeof(Type)) == 0;
}

struct St {
    bool a_bool;
    int an_int;
};
union Un {
    char buff[sizeof(St)];
    St st;
};

std::ostream &operator<<(std::ostream & out, const St& data)
{
   return out << '{' << std::boolalpha << data.a_bool << ", " << data.an_int << '}';
}

int main()
{
    Un un{{1,2,3,4,5}};
    new (&un.st) St;
    un.st.a_bool = true;
    un.st.an_int = 5;

    St x={true, 5};
    std::cout << "un.a=" << un.st << '\n';
    std::cout << "x=" << x << '\n';
    std::cout << (x == un.st) << "\n";
    return 0;
}

Both un.st and x contain the same data, but un.st contains some garbage in the padded bytes. The padded garbage makes the propose operator== return false for logically equivalent objects. Here is the output I have got for both gcc (head-9.0.0) and clang (head-8.0.0):

un.a={true, 5}
x={true, 5}
false

Update: this happens also with regular new/delete, as run on wandbox.org:

std::enable_if_t<std::is_pod<Type>::value, bool> operator==(const Type& a,
                                                            const Type& b) 
{
  return std::memcmp(&a, &b, sizeof(Type)) == 0;
}

struct St {
    bool a_bool;
    int an_int;
};

std::ostream &operator<<(std::ostream & out, const St& data)
{
   return out << '{' << std::boolalpha << data.a_bool << ", " << data.an_int << '}';
}

static constexpr unsigned N_ELEMENTS = 2;
int main()
{
    {
        volatile char * arr = new char[sizeof(St) * N_ELEMENTS];
        for (unsigned i=0; i < sizeof(St) * N_ELEMENTS ; ++i)
            arr[i] = i + 1;
        std::cout << "arr = " << (void*)arr << "\n";
        delete[] arr;
    }

    St * ptr_st = new St[N_ELEMENTS];
    std::cout << "ptr_st = " << ptr_st << "\n";
    for (unsigned i=0 ; i != N_ELEMENTS; ++i) {
       ptr_st[i].a_bool = true;
       ptr_st[i].an_int = 5;
    }
    St x={true, 5};
    std::cout << "x=" << x << '\n';
    std::cout << "ptr_st[1]=" << ptr_st[1] << '\n';
    std::cout << (x == ptr_st[1]) << "\n";
    return 0;
}

For which the output is:

arr = 0x196dda0
ptr_st = 0x196dda0
x={true, 5}
ptr_st[1]={true, 5}
false
Giorgi answered 22/8, 2018 at 6:8 Comment(2)
This either will or will not fail (depending on your goals) for floating-point types for which !(NAN == NAN) even though the two NANs are bitwise identical. This can be a huge gotcha in some cases (like putting NANs in std::set<float>).Spier
@Spier I agree that NaN can also break OP's proposal. I can think of a third way it can subtly break (with a union). I will not update the answer, since it is sufficient to show one caseGiorgi

© 2022 - 2024 — McMap. All rights reserved.