16

I've defined an IntWrapper class like the following one:

struct IntWrapper
{
protected:
  int value;

public:
  explicit IntWrapper() = default;
  explicit IntWrapper(const int value) : value(value) {}

  bool operator< (const IntWrapper rhs) const { return value <  rhs.value; }
  bool operator> (const IntWrapper rhs) const { return value >  rhs.value; }
  bool operator<=(const IntWrapper rhs) const { return value <= rhs.value; }
  bool operator>=(const IntWrapper rhs) const { return value >= rhs.value; }
  bool operator==(const IntWrapper rhs) const { return value == rhs.value; }

  explicit operator int() const { return value; }
};

and the classes Foo and Bar that inherit from IntWrapper

struct Foo: IntWrapper
{
  using IntWrapper::IntWrapper;
};

struct Bar: IntWrapper
{
  using IntWrapper::IntWrapper;
};

I'd like to compare only objects of the same type. In other words I'd like the following piece give compilation error, instead of casting foo and bar to IntWrapper.

const Foo foo(1);
const Bar bar(2);
bool b = foo >= bar;

Since I have many other objects like Foo and Bar, in there any way to achieve my result keeping all the comparison operators inside IntWrapper?

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
c.bear
  • 1,197
  • 8
  • 21
  • Is it just the comparisons you want to disallow? Would you be fine with adding a "useless" template param to IntWrapper, this would make them different bases because you would end up having: `struct Foo : IntWrapper` – Borgleader May 11 '18 at 13:15
  • 4
    Sounds like Bar and Foo aren't an IntWrapper, but should contain an IntWrapper. – UKMonkey May 11 '18 at 13:17
  • @UKMonkey If you do that, you have to implement the interface for the operators in every class (even if they just forward to intwrapper) which I'm assuming is what OP is trying to avoid ? – Borgleader May 11 '18 at 13:20

3 Answers3

9

You can add a dummy template to your IntWrapper to make the comparison operators work for same-type IntWrapper only:

template<class>
struct IntWrapper
{ /* same code */ };

struct Foo : IntWrapper<Foo> { using IntWrapper::IntWrapper; };
struct Bar : IntWrapper<Bar> { using IntWrapper::IntWrapper; };

int main()
{
    const Foo foo(1);
    const Bar bar(2);
    //bool b = foo >= bar; // error: no match for 'operator>=' (operand types are 'const Foo' and 'const Bar')
}

live demo

YSC
  • 38,212
  • 9
  • 96
  • 149
  • This is not a solution. You prevent any inheritance from `Foo`. – llllllllll May 11 '18 at 13:18
  • @liliscent yep, I also find this could be a good solution. Could you explain a little more what you mean? – c.bear May 11 '18 at 13:23
  • 2
    If you have 2 different classes deriving form `Foo`, they *can* compare in your code, which is not the OP's expected result. The problem here is you need to keep track of the most derived one in the most base class, a complete solution will make everything a template. – llllllllll May 11 '18 at 13:24
  • 2
    That's assuming there is more than one inheritance level. My understanding is that this is a generic typesafe int type meant to be inherited from whenever you need a new kind of type safe int and the inheritance "tree" stops there. – Borgleader May 11 '18 at 13:24
  • 1
    If you don't need more than one inheritance level make it `final` and be done with it – Marco A. May 11 '18 at 13:31
  • @liliscent I get it. That's true I've assumed there was only one level of inheritance. Does it suits OP? – YSC May 11 '18 at 13:43
5

I'd like to compare only objects of the same type.

If you want two IntWrapper instances to be comparable as such, but not instances of classes which inherit from IntWrapper, you're practically saying you don't really want these classes to inherit from IntWrapper.

  • @YSC suggested you prevent this inheritance using the CRTP pattern, so that each class inherits its "own" IntWrapper. That would prevent code duplication.
  • You could alter the comparison operator so that it checks for the elements both being of the same subclass of IntWrapper.
  • You could relegate the comparability to a subclass of IntWrapper, which Foo and Bar don't inherit.

And there are probably more ways to achieve this. But, again, it sounds like a questionable design.

einpoklum
  • 118,144
  • 57
  • 340
  • 684
4

Use templates for these operators and declare them as friends.

template <typename T, 
          std::enable_if_t<std::is_base_of_v<IntWrapper, T>, int> = 0> 
friend bool operator< (T lhs, T rhs) { return lhs.value <  rhs.value; }

Now if the real types of arguments are different, T cannot be deduced correctly.


Note I do not use const T because top-level const of function parameter is ignored.

xskxzr
  • 12,442
  • 12
  • 37
  • 77
  • 1
    While this should work, the intent may not be obvious to future maintainers/developers, and I would be worried about bugs due to code clashing with this. – einpoklum May 11 '18 at 15:05
  • 2
    @einpoklum This is what comments do. – xskxzr May 11 '18 at 15:08
  • 1
    `std::enable_if_t>` is pretty clear intent. If you need extra, documentation along the lines of "shall not participate in overload resolution unless ..." like how the standard describes it's constrained templates – Caleth May 11 '18 at 15:33
  • @Caleth: That is absolutely not clear intent, since you have to: 1. Be aware that the code exists. 2. Know some template metaprogramming to read it 3. Know that implicit conversion won't happen. – einpoklum May 11 '18 at 20:18
  • @einpoklum 1. it's in the declaration of the class (see `friend`). 2. It contains the words "enable if is base of" 3. implicit conversion from what to what? A deduced type to itself? – Caleth May 11 '18 at 21:57
  • I'm telling you that a non-experienced user would not understand that the author of this code specifically intended for IntWrapper's to be comparable, but different subclasses of IntWrapper's not to be, even as IntWrapper's themselves. If I need to remind myself of that looking at your code - which again, is a clever way to achieve this effect - then less-experienced users who do not know what they're supposed to look out for will very likely miss it. – einpoklum May 11 '18 at 23:04