'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)

  • operator== in terms of operator!=
  • operator!= in terms of operator==
  • Separate implementations for == and !=
  • As member or as free functions?

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



Solution 1:[1]

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.

Solution 2:[2]

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;

  // ...
};

Solution 3:[3]

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.

Solution 4:[4]

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.

Solution 5:[5]

(-: 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.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 hcb
Solution 2 T33C
Solution 3 E_net4 - Krabbe mit Hüten
Solution 4 Nim
Solution 5