Comparison tricks in C++

c++, comparison

Solution

The easy way

The simplest approach is to write a member function wrapper called `in()` around `std::find` with a pair of iterators to look for the data in question. I wrote a simple `template<class It> in(It first, It last)` member function for that

template<class It>
bool in(It first, It last) const
{
    return std::find(first, last, data) != last;
}

If you have no access to the source of `foo`, you can write a non-member functions of signature `template<class T> bool in(foo const&, std::initializer_list<T>)` etc., and call it like

in(f, {1, 2, 3 });

The hard way

But let's go completely overboard with that: just add two more `public` overloads:

- one taking a `std::initializer_list` parameter that calls the previous one with the `begin()` and `end()` iterators of the corresponding initializer list argument.

- one for an arbitrary container as input that will do a little tag dispatching to two more `private` overloads of a `detail_in()` helper:

- one overload doing a SFINAE trick with trailing return type `decltype(c.find(data), bool())` that will be removed from the overload set if the container `c` in question does not have a member function `find()`, and that returns `bool` otherwise (this is achieved by abusing the comma operator inside `decltype`)

- one fallback overload that simply takes the `begin()` and `end()` iterators and delegates to the original `in()` taking two iterators

Because the tags for the `detail_in()` helper form an inheritance hierarchy (much like the standard iterator tags), the first overload will match for the associative containers `std::set` and `std::unordered_set` and their multi-cousins. All other containers, including C-arrays, `std::array`, `std::vector` and `std::list`, will match the second overload.

#include <algorithm>
#include <array>
#include <initializer_list>
#include <type_traits>
#include <iostream>
#include <set>
#include <unordered_set>
#include <vector>

class foo
{
public:
    int data;

    template<class It>
    bool in(It first, It last) const
    {
        std::cout << "iterator overload: ";
        return std::find(first, last, data) != last;
    }

    template<class T>
    bool in(std::initializer_list<T> il) const
    {
        std::cout << "initializer_list overload: ";
        return in(begin(il), end(il));
    }

    template<class Container>
    bool in(Container const& c) const 
    {
        std::cout << "container overload: ";
        return detail_in(c, associative_container_tag{});    
    }

private:
    struct sequence_container_tag {};
    struct associative_container_tag: sequence_container_tag {};

    template<class AssociativeContainer>
    auto detail_in(AssociativeContainer const& c, associative_container_tag) const 
    -> decltype(c.find(data), bool())
    {
        std::cout << "associative overload: ";
        return c.find(data) != end(c);    
    }

    template<class SequenceContainer> 
    bool detail_in(SequenceContainer const& c, sequence_container_tag) const
    {
        std::cout << "sequence overload: ";
        using std::begin; using std::end;
        return in(begin(c), end(c));    
    }
};

int main()
{
    foo f{1};
    int a1[] = { 1, 2, 3};
    int a2[] = { 2, 3, 4};

    std::cout << f.in({1, 2, 3}) << "\n";
    std::cout << f.in({2, 3, 4}) << "\n";

    std::cout << f.in(std::begin(a1), std::end(a1)) << "\n";
    std::cout << f.in(std::begin(a2), std::end(a2)) << "\n";

    std::cout << f.in(a1) << "\n";
    std::cout << f.in(a2) << "\n";

    std::cout << f.in(std::array<int, 3>{ 1, 2, 3 }) << "\n";
    std::cout << f.in(std::array<int, 3>{ 2, 3, 4 }) << "\n";

    std::cout << f.in(std::vector<int>{ 1, 2, 3 }) << "\n";
    std::cout << f.in(std::vector<int>{ 2, 3, 4 }) << "\n";

    std::cout << f.in(std::set<int>{ 1, 2, 3 }) << "\n";
    std::cout << f.in(std::set<int>{ 2, 3, 4 }) << "\n";

    std::cout << f.in(std::unordered_set<int>{ 1, 2, 3 }) << "\n";
    std::cout << f.in(std::unordered_set<int>{ 2, 3, 4 }) << "\n";    
}

Live Example that -for all possible containers- prints 1 and 0 for both number sets.

The use cases for the `std::initializer_list` overload are for member-ship testing for small sets of numbers that you write out explicitly in calling code. It has `O(N)` complexity but avoids any heap allocations.

For anything heavy-duty like membership testing of large sets, you could store the numbers in an associative container like `std::set`, or its `multi_set` or `unordered_set` cousins. This will go to the heap when storing these numbers, but has `O(log N)` or even `O(1)` lookup complexity.

But if you happen to have just a sequence container full of numbers around, you can also throw that to the class and it will happily compute membership for you in `O(N)` time.

Problem

A class: ``` class foo{ public: int data; }; ``` Now I want to add a method to this class, to do some comparison, to see if its data is equal to one of given numbers. Of course, I can write `if(data==num1|| data == num2|| data ==num3.....)`, but honestly speaking, I feel sick when I write `data ==` every time I compare it to a number. So, I hope I would be able to write something like this: ``` if(data is equal to one of these(num1,num2,num3,num4,num5...)) return true; else return false; ``` I want to implement this statement, `data is equal to one of these(num1, num2, num3, num4, num5...)` Here is my approach: ``` #include <stdarg.h> bool is_equal_to_one_of_these(int count,...){ int i; bool equal = false; va_list arg_ptr; va_start(arg_prt,count); for(int x=0;x<count;x++){ i = va_arg(arg_ptr,int); if( i == data ){ equal = true; break; } } va_end(arg_ptr); return equal; } ``` This piece of code will do the job for me. But every time I use this method, I'll have to count the parameters and pass it in. Does anyone have a better idea?

Original source

Related problems