Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::set select less or greater comparator at runtime

I was refactoring some code and found there are two places that can be written with the same code except the comparator of a set is less<double> in one place and greater<double> in the other. Something like:

double MyClass::Function1(double val)
{
    std::set<double, less<double> > s;
    // Do something with s
}

double MyClass::Function2(double val)
{
    std::set<double, greater<double> > s;
    // Do the same thing with s as in Function1
}

So I thought of doing:

double MyClass::GeneralFunction(double val, bool condition)
{  
    if(condition)  
    {  
        // Select greater as comparator  
    }  
    else
    {  
        // Select less as comparator  
    }  

    set<double, comparator> s;  
    // common code
}

I've made it work by using my custom comparator functions, like this:

bool my_greater(double lhs, double rhs)
{
    return lhs > rhs;
}

bool my_less(double lhs, double rhs)
{
    return lhs < rhs;
}

double MyClass::GeneralFunction(double val, bool condition)
{ 
    typedef bool(*Comparator) ( double,  double);
    Comparator comp = &my_less;
    if (condition)
    {
        comp = &my_greater;
    }

    std::set<double, Comparator > s(comp);  

    //....
}

But I would like to use the built-in ones. The problem is I don't know how to declare the comparator and assign it the built in predicates.

Any help would be greatly appreciated.

like image 487
MikMik Avatar asked Jun 25 '12 07:06

MikMik


People also ask

How do you create a custom comparator for a set?

auto cmp = [](int a, int b) { return ... }; std::set<int, decltype(cmp)> s; We use lambda function as comparator. As usual, comparator should return boolean value, indicating whether the element passed as first argument is considered to go before the second in the specific strict weak ordering it defines.

Does c++ set maintain order?

A Set stores the elements in sorted order. Set stores unique elements. Elements can only be inserted and deleted but cannot be modified within the set.

Is c++ set sorted?

Sets are a type of associative container in which each element has to be unique because the value of the element identifies it. The values are stored in a specific sorted order i.e. either ascending or descending.

Are std :: sets ordered?

Per the C++ standard, iteration over the elements in an std::set proceeds in sorted order as determined by std::less or by the optional comparison predicate template argument.


2 Answers

Do you really need a runtime check?

template <class Comp> double MyClass::Function(double val)
{
    std::set<double, Comp > s;
    // Do something with s
}

Even if you do, you can still use

double MyClass::Function(double val, bool comp)
{
   return comp ? Function<std::less<double> >(val) : Function<std::greater<double> >(val);
}
like image 169
MSalters Avatar answered Oct 19 '22 07:10

MSalters


The problem is that you cannot choose the type of the comparator at tuntime, and std::less and std::greater have unrelated types. Similarly, an std::set instantiated with std::less as a comparator has a type unrelated to on instantiated with std::greater. There are several possible solutions, but the simplest (and the only one not involving inhertance, virtual functions and dynamic allocation) is along the lines of what you are doing:

class SelectableCompare
{
    bool myIsGreater;
public:
    SelectableCompare( bool isGreater ) : myIsGreater( isGreater ) {}
    bool operator()( double d1, double d2 ) const
    {
        static std::less<double> const less;
        return myIsGreater
            ? less( d2, d1 )
            : less( d1, d2 );
    }
};

I've used the standard std::less and std::greater because you expressed an interest in doing so. In the case of double, this is, frankly, overkill; I'd normally just write d1 > d2 and d1 < d2. A templated version of the above, however, might make sense, since some types might have a specialized std::less. This is also why I only use std::less; it's quite conceivable that a programmer specialize only std::less, with the knowledge that this is the only one used for ordering in the standard library.

Just to be complete: the obvious alternative is to use the strategy pattern in the comparator, with an abstract comparator base:

class Comparator
{
public:
    virtual ~Comparator() {}
    virtual bool isLessThan( double d1, double d2 ) const = 0;
};

, the rather obvious derived classes for the different comparisons, and a wrapper to manage the memory:

class ComparatorWrapper
{
    std::shared_ptr<Comparator> myComparator;
public:
    ComparatorWrapper( Comparator* newed_comparator )
        : myComparator( newed_comparator )
    {
    }
    bool operator()( double d1, double d2 ) const
    {
        return myComparator->isLessThan( d1, d2 );
    }
};

This is definitely overkill for the binary choice you need, but might be appropriate if there were more choices; e.g. a set which might be sorted on one of many different fields (all of different types).

like image 4
James Kanze Avatar answered Oct 19 '22 07:10

James Kanze