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.
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);
}
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; eg a set
which might be sorted on one of many different fields (all of different types).
Just use
std::set<double, std::function<bool(double,double)>>
as your set, and instantiate it like so:
typedef std::set<double, std::function<bool(double,double)> > RTSet;
RTSet choose_ordering(bool increasing)
{
if (increasing)
return RTSet( std::less<double>() );
else
return RTSet( std::greater<double>() );
}
Note in general your tradeoff is to either:
I'm preferring the second option so you can't accidentally change the ordering while a set is in use, breaking all its invariants.
Just a quick thought, as this could potentially be a separate answer (and even question), but you mention two bits of code are identical except for sort order.
An alternative I've used in some situations, is to use a single sort direction, and template the code operating on the set (by iterator type), so you can do
if (increasing)
do_stuff(set.begin(), set.end());
else
do_stuff(set.rbegin(), set.rend());
Why not do
template <typename Compare>
double MyClass::GeneralFunction(double val)
{
std::set<double, Compare> s;
//....
}
Template selection by formal parameter is not something C++ handles very well. Push as much as possible into the compilation phase by having the caller supply the template argument.
Then you can provide a wrapper, if you really want to select one at runtime:
double MyClass::GeneralFunction(double val, bool condition)
{
return condition ?
GeneralFunction<std::greater<double> >(val) :
GeneralFunction<std::less <double> >(val);\
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.