5

I often have classes which provide simple member-by-member comparison:

class ApplicationSettings
{
public:
   bool operator==(const ApplicationSettings& other) const;
   bool operator!=(const ApplicationSettings& other) const;

private:
   SkinType m_ApplicationSkin;
   UpdateCheckInterval m_IntervalForUpdateChecks;
   bool m_bDockSelectionWidget;
   // Add future members to operator==
};

bool ApplicationSettings::operator==(const ApplicationSettings& other) const
{
   if (m_ApplicationSkin != other.m_ApplicationSkin)
   {
      return false;
   }

   if (m_IntervalForUpdateChecks != other.m_IntervalForUpdateChecks)
   {
      return false;
   }

   if (m_bDockSelectionWidget != other.m_bDockSelectionWidget)
   {
      return false;
   }

   return true;
}

bool ApplicationSettings::operator!=(const ApplicationSettings& other) const;
{
   return ( ! operator==(other));
}

Given that C++ at this time does not provide any construct to generate an operator==, is there a better way to ensure future members become part of the comparison, other than the comment I added below the data members?

Asperamanca
  • 143
  • 4
  • Future C++ versions might provide some help on that. See [CppCon2017 Herb Sutter on "Meta: Thoughts on generative C++"](https://youtu.be/4AfRAVcThyA) talk. But today I see no way to check or generate that automatically – Basile Starynkevitch Mar 06 '18 at 09:46
  • 3
    [`std::tie` can help](https://stackoverflow.com/q/6218812/3484570). – nwp Mar 06 '18 at 09:48
  • @nwp - You still need to add the new member into the call to `tie`. – StoryTeller - Unslander Monica Mar 06 '18 at 09:53
  • You can also write a function that gives you a `constexpr` tuple of pointers to members, so you don't need to duplicate the list of members. There is a question somewhere on SO that shows that off, but I can't find it. It also mentions [magic get](https://github.com/apolukhin/magic_get). – nwp Mar 06 '18 at 09:53
  • 2
    [This is an example](https://github.com/Toeger/SCE/blob/001cfd58ab7266d91cae93f3076eb8eeca526886/logic/tool.h?ts=4#L28) of the member pointer tuple I mentioned. – nwp Mar 06 '18 at 10:05
  • A related interesting question https://stackoverflow.com/questions/217911/why-dont-c-compilers-define-operator-and-operator – llllllllll Mar 06 '18 at 10:23

2 Answers2

8

It doesn't catch every case, and annoyingly it's compiler and platform dependent, but one way is to static_assert based on the sizeof of the type:

static_assert<sizeof(*this) == <n>, "More members added?");

where <n> is a constexpr.

If new members are introduced then, more often than not, sizeof changes, and you'll induce a compile time failure.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
  • Brilliant, but how to compute `n`? – p-a-o-l-o Mar 06 '18 at 09:52
  • @p-a-o-l-o: A `printf` call to get the value, then edit the source code. Yes, it's not great. – Bathsheba Mar 06 '18 at 09:53
  • Can be useful anyway. – p-a-o-l-o Mar 06 '18 at 09:54
  • Alternatively, locally declare a dummy class with the same members and use it in the right side of the comparison. This will be more portable. –  Mar 06 '18 at 09:56
  • Would it be possible to compute the expected sizeof based on the last member compared in operator== (given that this is also the last member in the memory layout of the class)? It would only make a runtime check possible, but it at least the check would be right where the offending code is. – Asperamanca Mar 06 '18 at 12:13
  • @p-a-o-l-o: For editors with C++ syntax support (primarily Intellisense in Visual Studio), try instantiating `template class A;` with `A`. The editor will probably complain that `A<16>` is missing. – MSalters Mar 06 '18 at 15:02
4

Focusing solely on the technical aspect of this, you can leverage the fact the standard library std::tuple type overloads operator== for member-wise comparison. If you don't mind sacrificing simple member access elsewhere, you can just wrap your members in a tuple. Something like this:

#include <tuple>

class ApplicationSettings
{
public:
   bool operator==(const ApplicationSettings& other) const;
   bool operator!=(const ApplicationSettings& other) const;

private:

   enum m {
     ApplicationSkin, 
     IntervalForUpdateChecks,
     bDockSelectionWidget
   };

   std::tuple<
     SkinType,
     UpdateCheckInterval,
     bool
   > m_Data;
};

Now implementing the comparison operator is a no-brainer:

bool ApplicationSettings::operator==(const ApplicationSettings& other) const {
  m_Data == other.m_Data;
}

Of course, the sacrifice is that other member functions need to access other members via std::get<m::ApplicationSkin>(m_Data). Which could raise a fair few eyebrows.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • This should even allow me to handle some corner cases: If I use std::equal_to, I can provide a custom comparison template method that handles special members like floats. Also, I can order the tuple in the most performance-efficient way (simple-to-compare members first) – Asperamanca Mar 06 '18 at 10:10
  • @Asperamanca - I didn't consider that. You are right of course. It may even make the split of the member type from its identifier-turned-integral-constant more tolerable. Still, don't jump the gun with this. Consider the trade-offs carefully. – StoryTeller - Unslander Monica Mar 06 '18 at 10:14
  • I think `enum class` isn't a good choice (template argument deduction/substitution fails). – p-a-o-l-o Mar 06 '18 at 10:26
  • 1
    I'm not a fan of this answer - and I'm not even sure it actually solves the issue. It adds complexity and confusion to an uninformed reader - "wtf are all those things added as a tuple?" and it just makes things plain ugly. If I came across this class and needed to add a member would I add it to the tuple or just add it as another member? Sure now I've read this answer I'd add it to the tuple but I'm not convinced a non informed programmer necessarily would. – Mike Vine Mar 06 '18 at 10:26
  • @MikeVine - I wrote it and I'd be hesitant to recommend it myself. Like I started with, I focused solely on the technical. This is just about showing feasibility. The OP wants the operators to not need updating if a data member is added. So there it is. You can downvote if you think this isn't useful, no hard feelings. – StoryTeller - Unslander Monica Mar 06 '18 at 10:28
  • @p-a-o-l-o - That implicit conversion always gets me! I just wanted to force the scope resolution operator to be used. Fixed, thanks. – StoryTeller - Unslander Monica Mar 06 '18 at 10:29
  • You're welcome, @StoryTeller. I like your solution very much, and wanted to test it. Anyway, I met a problem and made a question out of it: https://stackoverflow.com/questions/49129831/stdtuple-member-by-member-comparison-fails. Can you take a look at it? – p-a-o-l-o Mar 06 '18 at 11:34