37

I have to overload a == operator in C++ for a class with many attributes.
The operator should return true, if and only if all attributes are equal. A shortcut might be useful, if these attributes change over time, to avoid bugs.

Is there a shortcut to compare every attribute in a class?

rohithpr
  • 6,050
  • 8
  • 36
  • 60
Mehno
  • 868
  • 2
  • 8
  • 21
  • You can use memcmp if your object is POD or some large part of it POD(on that part) – VladimirS Feb 29 '16 at 16:32
  • You can write a script (in your editor if it supports it. Vim regular expression substitutions could do it, for example) to take a copy of the declaration lines and turn them into `element == other.element &&` – Zan Lynx Feb 29 '16 at 16:34
  • 8
    @user3545806 `memcmp` won't account for padding, so that won't work. – Barry Feb 29 '16 at 16:35
  • @Barry, will it work if you memset POD part first and then use memcmp? But obviously you are right, my comment has missing that note. – VladimirS Feb 29 '16 at 16:44
  • @user3545806 Yeah, but then that requires somebody to have done something external to the `operator==` so you can't guarantee that it'll actually do the right thing. – Barry Feb 29 '16 at 16:49
  • Some might say, if you have so many attributes that it is possible to forget one then your class has too many attributes and should be broken up into classes with fewer attributes. – user3467895 Feb 29 '16 at 16:53
  • @Barry, generally I agree. IMHO: It would be faster in runtime but it would not be for free, and obviously it will not work with none PODs. but with data driven design sometimes it possible to move out POD types and compare them separately. – VladimirS Feb 29 '16 at 17:07
  • @user3545806 you cannot `memcmp` bunch of types, such `std::string`. – ST3 Mar 02 '16 at 10:39
  • @ST3 Yes that is correct I can not memcmp std::string. But I said POD types. I can memcmp pod types. imagine an object with 50 longs and one std::string. I can split it so I can memcmp 50 longs and then do operator == on string. From the other prospective, compiler may do this to me automatically. – VladimirS Mar 02 '16 at 16:09
  • @user3545806 Oh... Sorry, now see it – ST3 Mar 02 '16 at 21:13
  • 1
    @VladimirS (and @Barry) regarding `memcmp` - I think it's worse than you're stating here. Even with POD, even if the POD is pre-initialized (e.g. zero'd), the user can get burned in a corner case with a discriminated union. Let's say the user has a union with a char and an int (suppose 8 bits and 32 bits), and a "tag" outside the union to discriminate if we should read the char or the int from the union. If the "char" is "active" in the union per the tag, and semantically they are identical, the `memcmp` could still fail. – Dan Mar 06 '16 at 15:08
  • @Dan Good point. Yes there are an exceptions to memcmp usage. However there are possible benefits sometimes. Overall Use with care! If you do not need to squeeze extra ticks Barry answer bellow is the best. – VladimirS Mar 06 '16 at 16:31

5 Answers5

50

There is no shortcut. You will have to list everything.

Some sources of error can be reduced by introducing a member function named tied() like:

struct Foo {
    A a;
    B b;
    C c;
    ...

private:
    auto tied() const { return std::tie(a, b, c, ...); }
};

So that your operator== can just use that:

bool operator==(Foo const& rhs) const { return tied() == rhs.tied(); }

This lets you only list all your members once. But that's about it. You still have to actually list them (so you can still forget one).


There is a proposal (P0221R0) to create a default operator==, but I don't know if it will get accepted.


The above proposal was rejected in favor of a different direction regarding comparisons. C++20 will allow you to write:

struct Foo {
    A a;
    B b;
    C c;

    // this just does memberwise == on each of the members
    // in declaration order (including base classes)
    bool operator==(Foo const&) const = default;
};
Barry
  • 286,269
  • 29
  • 621
  • 977
19

Starting in C++11 with the introduction of tuples we also got std::tie(). This will let use make a tuple out of a bunch of variables and call a comparison function against all of them. You can use it like

struct Foo
{
    int a,b,c,d,e,f;
    bool operator==(const Foo& rhs) { return std::tie(a,b,c,d,e,f) == std::tie(rhs.a,rhs.b,rhs.c,rhs.d,rhs.e,rhs.f); }
};

You still have to list all the members you want to check but it makes it easier. You can also use this to make less than and greater than comparisons much easier.

It should also be noted that the variables are checked in the order you provide them to tie. This is important for less than and greater than comparisons.

std::tie(a,b) < std::tie(rhs.a, rhs.b);

Does not have to be the same as

std::tie(b,a) < std::tie(rhs.b, rhs.a);
Barry
  • 286,269
  • 29
  • 621
  • 977
NathanOliver
  • 171,901
  • 28
  • 288
  • 402
5

At the moment, there is no shortcut but there are plans to add support for it (P0221R0).

Bjarne Stroustrup recently wrote a blog post about it: A bit of background for the default comparison proposal

In C++14, there is nothing better than listing all members and comparing them, which is error prone. To quote Bjarne:

The killer argument for default comparisons is not actually convenience, but the fact that people get their equality operators wrong.

Philipp Claßen
  • 41,306
  • 31
  • 146
  • 239
2

The only way of doing it is unfortunatly to check all attributes. The good thing is if you combine all your checks using && it will stop evaluating after the first false statement. (short-circuit evaluation)

So e.g. false && (4 == 4). The program would never evaluate the 4 == 4 part since all statements combined by && need to be true to get true as a final outcome. Does this make sense?

chue x
  • 18,573
  • 7
  • 56
  • 70
muXXmit2X
  • 2,745
  • 3
  • 17
  • 34
1

There is a not to the operator==related solution possible. You can generate the related code from a definition table with the help of so called X-Macro. The table could look like

#define MEMBER_TBL                    \
/*type        ,name ,default*/        \
X(int         ,_(i) ,42     )         \
X(float       ,_(f) ,3.14   )         \
X(std::string ,  t  ,"Hello")         \

The _()stuff is needed to avoid a trailing , on generation the std::tie() call. Make sure that the last element is w.o. _(). The usage to generate the members is:

struct Foo
{
#define _(x) x
#define X(type, name, default) type name{default};
    MEMBER_TBL
#undef X
#undef _
}

This generates:

struct Foo
{
    int i{42}; float f{3.14}; std::string t{"Hello"};
}

To generate the operator==you can use:

bool operator==(Foo const& other) const {
        return  std::tie(
#define _(x) x,
#define X(type, name, default) this->name
            MEMBER_TBL
#undef X
        ) == std::tie(
#define X(type, name, default) other.name
            MEMBER_TBL
#undef X
#undef _
        );
    }

which results into

bool operator==(Foo const& other) const {
    return std::tie(
                     this->i, this->f, this->t
    ) == std::tie(
                  other.i, other.f, other.t
    );
}

To add new members you can add simply a new entry to the first table. Everything else is generated automatically.

Another advantage is, you can add simply a dump() method like

void print(void) const { 
    #define STR(x) #x
    #define _(x) x
    #define X(type, name, default)            \
            std::cout <<                      \
                STR(name) << ": " << name << " ";
            MEMBER_TBL
    #undef X
    #undef _
    #undef STR
            std::cout << std::endl;
        }

which results into

void print() const {
    std::cout << "i" << ": " << i << " "; std::cout << "f" << ": " << f << " "; std::cout << "t" << ": " << t << " ";
    std::cout << std::endl;
}

Every information regarding to the members could be added to the table at one place (single point of information) and extracted elsewhere needed.

A working Demo.

kwarnke
  • 1,424
  • 1
  • 15
  • 10
  • It is worth mentioning that this technique should be reserved for very expectional cases only. For instance, I used it in one example where I had a class of config parameters. Whenever you had to introduced a new parameter, you had to update several location in the code, which was quite error prone. IMHO using the X Macro approach simplified the maintainance but it remains a bit controversal, so I would only recommend it as a last resort. But knowing that the technique exists, is indeed useful. – Philipp Claßen Mar 03 '16 at 22:12