0

The project I am working on involves a class, let it be myClass, which is similar to a set, and I need to allow the programmer to compare objects of this type in a meaningful way.

What I would like to be able to do is something like this:

myClass a, b;
...
if (a == b)
{
    //execute code where a and b are implicitly used as
    // each of a and b's elements, respectively
}

and have my custom if statement execute the conditional code for each pair of elements from a and b, based on the condition performed on the pair of elements.

Here is a more concrete example:

myClass a = {1, 2}, b = {2, 3};

if (a == b)
    std::cout << a << " equals " << b << std::endl;
else
    std::cout << a << " does not equal " << b << std::endl;

Where the result would be (not necessarily in this order):

1 does not equal 2
2 equals 2
1 does not equal 3
2 does not equal 3

Currently, I have comparison operators overloaded to return a "comparison type" which simply stores the two operands and the comparison function for lazy evaluation. Is there a way to accomplish this custom if/else behavior so that it occurs whenever an if statement receives a parameter of type "comparison type?" Or is it necessary to just define a regular function which accepts an object of this type and a reference to a binary operator function as the conditional code?

I saw this question, but simply defining a conversion to bool will not work in this case. C++ 'overloading' the if() statement

πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
wwilliamcook
  • 181
  • 1
  • 8
  • 1
    https://stackoverflow.com/questions/10575766/comparison-operator-overloading Overload the == operator and then use boolean if/else logic – Matthew Fisher Aug 29 '18 at 02:35
  • 2
    Just read it again. No, you can't do this. You probably can achieve something similar with [std::next_permutation](https://en.cppreference.com/w/cpp/algorithm/next_permutation) and [boost::zip_iterator](https://www.boost.org/doc/libs/1_68_0/libs/iterator/doc/zip_iterator.html), but, I mean, it's much easier (and clearer) to just write two nested `for`'s and iterate by hand. – Not a real meerkat Aug 29 '18 at 02:39
  • 1
    "*simply defining a conversion to bool will not work in this case*" - why not? That is exactly what an `if` statement expects. If you overload `operator==` to return a "comparison type" rather than `bool`, that type MUST be convertible to `bool`. See [What are the basic rules and idioms for operator overloading?](https://stackoverflow.com/questions/4421706/). Internally, you can compare elements however you want, as long as the final result can be represented as a `bool`. If you are using C++20, have a look at the new `operator<=>` instead, which is more flexible. – Remy Lebeau Aug 29 '18 at 02:44
  • 1
    No, you can't change the meaning of `if` and `else` to execute the controlled statements more times. You could get something similar with a different syntax, maybe something like `each_elem_if_else(a == b, [](const auto& x, const auto& y) { std::cout << x << " equals " << y << std::endl; }, [](const auto& x, const auto& y) { std::cout << x << " does not equal " << y << std::endl; });` – aschepler Aug 29 '18 at 02:47
  • @RemyLebeau Note the desired result involves executing the if-statement once and the else-statement three times. – aschepler Aug 29 '18 at 02:49
  • @RemyLebeau "To compare objects of this type in a meaningful way." Because reducing the *compound comparison* to a single bool would invalidate the comparison. Thanks for sharing `operator<=>`. – wwilliamcook Aug 29 '18 at 03:00
  • @aschepler That is what I had in mind as a backup, but would prefer to reuse the if-else syntax because it would better reflect the operation. Thanks for the confirmation. – wwilliamcook Aug 29 '18 at 03:03
  • @CássioRenan Thanks for the links. I agree that nested `for` loops are the best option either way, I just think it would be more concise and elegant to do all that under-the-hood. – wwilliamcook Aug 29 '18 at 03:08
  • If by "concise and elegant" you mean "cryptic and confusing' then I agree. Least surprise principle! – n. m. could be an AI Aug 29 '18 at 04:52
  • @n.m. That is true. Although I must add that it would be difficult to be surprised by an explicitly invoked feature... – wwilliamcook Aug 29 '18 at 05:04

2 Answers2

0

You can get this close:

myClass a = {1, 2}, b = {2, 3};

for(auto&& cmp:(a == b))
  if(cmp)
    std::cout << cmp.lhs << " equals " << cmp.rhs << std::endl;
  else
    std::cout << cmp.lhs << " does not equal " << cmp.rhs << std::endl;

simply define operator== on two myClass to return an iterable.

Each iterable element has a lhs, a rhs and a conversion to bool operator.

template<class Lhs, class Rhs=Lhs, class Cmp=std::equal<>>
struct comparison_t {
  Lhs lhs;
  Rhs rhs;
  operator bool()const{ return Cmp{}(lhs, rhs); }
};

struct myClass {
  int a, b;
  auto operator==( myClass const& lhs, myClass const& rhs ){
    std::array<comparison_t<int const&>> retval={{
      {lhs.a, rhs.a},
      {lhs.b, rhs.a},
      {lhs.a, rhs.b},
      {lhs.b, rhs.b},
    }};
    return retval;
  }
};

there are probably some typos.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • That is very clever, thanks a lot. The only issue I can see with it, though, is that for `myClass` operands with a large number of elements, expanding the comparison into a `std::array` will require a substantial amount of memory. Is there a way to work around this? – wwilliamcook Aug 29 '18 at 03:17
  • @wwill yes; look up what `for(:)` loops do. You just have to calculate what `*` should return and what `++` should do and how to handle end and `!=`. The array solution is just the easiest one. – Yakk - Adam Nevraumont Aug 29 '18 at 03:51
0

If you can get away from the if/else style, you can get the bodies of the if and else blocks as lambda to a helper function.

template<class T, class F> void compare_all(const myClass &a, const myClass &b, T fn_true, F fn_false) {
    for (const auto &ai: a.vec) {
        for (const auto &bi: b.vec) {
            if (ai == bi)
                fn_true(ai, bi);
            else
                fn_false(ai, bi);
        }
    }
}

The first two parameters to compare_all are the same as you'd have for operator==. The other two are the lambdas to hold the actions to do when the elements compare equal or not. The example here assumes there is a container named vec in the myClass class that holds the items to compare.

As an example for how it is used, here is how your original code can be rewritten to use this technique.

int main() {
    myClass a{1, 2}, b{2, 3};

    compare_all(a, b, 
        [](int a, int b) { std::cout << a << " equals " << b << std::endl; },
        [](int a, int b) { std::cout << a << " does not equal " << b << std::endl; }
    );
}
1201ProgramAlarm
  • 32,384
  • 7
  • 42
  • 56
  • This is close to what I had in mind in case the native if/else style couldn't be used. This answer could be tailored to the given problem by adding a `bool(condition)(const myClass& a, const myClass& b)` parameter to `compare_all` in lieu of the hard-coded `operator==`, as well as by making `fn_false` optional. Thank you. – wwilliamcook Aug 29 '18 at 04:43