Martin Ba
Martin Ba

Reputation: 38981

How to implement C++ (in)equality operators for aggregate structs?

Sometimes I have structs such as this --

struct aggregate1 {
  std::string name;
  std::vector<ValueT> options;
  size_t foobar;
  // ...
};

-- where (in)equality is simply defined as (in)equality of all members: lhs_name == rhs_name && lhs_options == rhs_options && lhs_foobar == rhs_foobar.

What's the "best" way to implement this? (Best as in: (Runtime-)Efficiency, Maintainability, Readability)

Note that this question is only about the (in)equality ops, as comparison (<, <=, ...) doesn't make too much sense for such aggregates.

Upvotes: 15

Views: 26671

Answers (5)

hcb
hcb

Reputation: 96

In C++20, implementing equality and inequality operators can be as simple as declaring operator== as default:

struct S {
  int x;
  // ...

  // As member function
  bool operator==(S const &) const = default;
  
  // As non-member function (hidden friend)
  // friend bool operator==(S const &, S const &) = default;
};

If only operator== is provided, a!=b is interpreted as !(a==b) according to overload resolution, so there is no need for providing an explicit overload for operator!=.

I would argue that defaulting operator== as a hidden friend is preferable because it works with reference-wrapped objects:

S s;
auto rs{std::ref(s)};
rs==rs; // OK for hidden friend; ill-formed if declared as member function

In this example, operator== is not defined for std::reference_wrapper<S>, but argument-dependent lookup (ADL) can select the hidden friend with operands implicitly-converted to S const &. Notice, however, that ::operator==(rs,rs) will only work if operator== is defined as a free function because ADL is not triggered for qualified names.

Upvotes: 4

Matteo Italia
Matteo Italia

Reputation: 126967

Member or free function is a matter of taste, and writing separate implementations of == and != seems to me boring, error-prone (you may forget a member in just one of the two operators, and it will take time to notice) without adding anything in terms of efficiency (calling the other operator and applying ! has a negligible cost).

The decision is restricted to "is it better to implement operator== in terms of operator!= or the contrary?

In my opinion, in terms of maintainability/readability/efficiency it's the same; I'd only recommend to do it in the same way everywhere for the sake of consistency. The only case where you'd want to prefer to use one or the other as the "base operator" is when you know that, in the types contained in your structure, that operator is faster than its negation, but I don't know when this could happen.

Upvotes: 9

Martin Ba
Martin Ba

Reputation: 38981

(-: Self answer :-)

I would like to highlight one aspect of aggregates WRT efficiency:

The order of evaluation of op== and op!= is irrelevant for (average) performance.

Assuming separate implementations for now and given the two extremes (a-eq) all subelements equal and (b-neq) all subelements inequal, we have these cases:

  • (a-eq) + operator== : Needs to compare all sub elements to return true
  • (a-eq) + operator!= : Needs to compare all sub elements to return false
  • (b-neq) + operator== : Returns false after 1st sub element is determined inequal
  • (b-neq) + operator!= : Returns true after 1st sub element is determined inequal

Since performance on average is the same either way it seems -- at least to me -- more natural to implement op!= in terms of op==, as it feels more natural to me to implement the equality op.

Upvotes: -1

T33C
T33C

Reputation: 4429

I would do this but maybe move operator== definition to cpp file. Leave operator!= to be inline

Remember to compare member variables that are most likely to differ first so the rest are short-circuited and performance is better.

struct aggregate1 {
  bool operator==(const aggregate1& rhs) const
  {
     return (name == rhs.name)
     && (options == rhs.options)
     && (foobar == rhs.foobar);
  }
  bool operator!=(const aggregate1& rhs) const
  {
    return !operator==(rhs);
  }

  std::string name;
  std::vector<ValueT> options;
  size_t foobar;

  // ...
};

Upvotes: 15

Nim
Nim

Reputation: 33655

IMHO, implement as friends and implement the operator== (some STL algorithms will rely on this for example) and the operator!= should be implemented as the negation of the equals operator.

Upvotes: 1

Related Questions