I'm trying to build an associative container with a customizable comparison predicate and I'm internally using a set to store some of the data. It should be pretty straightforward, I just need to pass along the predicate:
template <typename T, typename TComparePredicate = std::less<T>>
class MyContainer
{
public:
MyContainer(const TComparePredicate& predicate = TComparePredicate()) : _internalSet(predicate) { ... }
...
private:
std::set<T, TComparePredicate> _internalSet;
};
Unfortunately, my real case is more complex. I need to store other data in the set (don't ask why, it makes sense in this context), so my code looks more like this:
template <typename T, typename V, typename TComparePredicate = std::less<T>>
class MyContainer
{
public:
MyContainer(const TComparePredicate& predicate = TComparePredicate()) : _internalSet(???) { ... }
...
private:
std::set<std::pair<T, V>, ???> _internalSet;
};
I still need the set to be ordered only considering the T
value, so my simple solution is to create a functor that stores the real predicate and dispatches calls to it:
template <typename T, typename V, typename Predicate>
struct PairForwardingFunctor
{
PairForwardingFunctor(const Predicate& predicate = Predicate()) : _predicate(predicate) { }
bool operator()(const std::pair<T, V>& lhs, const std::pair<T, V>& rhs) { return _predicate(lhs.first, rhs.first); }
private:
Predicate _predicate;
};
But I feel uneasy with this solution. I haven't still compiler-explored this (if it can even get down there), but I have a feeling there is a level of indirection that is not optimizable.
Is there a better approach to this?
While working on a similar problem, I found out a solution which should be a little more efficient, despite being more verbose:
template <typename T, typename V, typename Predicate, typename = std::void_t<>>
struct PairForwardingFunctor : private Predicate
{
PairForwardingFunctor(const Predicate& predicate = Predicate()) : Predicate(predicate) { }
bool operator()(const std::pair<T, V>& lhs, const std::pair<T, V>& rhs) { return Predicate::operator()(lhs.first, rhs.first); }
};
template <typename T, typename V, typename Predicate>
struct PairForwardingFunctor<T, V, Predicate, std::void_t<std::is_pointer<Predicate>::type>
{
PairForwardingFunctor(const Predicate& predicate) : _predicate(predicate) { }
bool operator()(const std::pair<T, V>& lhs, const std::pair<T, V>& rhs) { return _predicate(lhs.first, rhs.first); }
private:
Predicate _predicate;
};
This avoids any internal object creation for lambda and function objects, which are way more likely to be optimized away. Function pointers are still stored, as I don't think it's possible to do otherwise with those.
It's obviously still a stub, as it needs more care about transparent comparison, but if nobody has a better idea I'm going to choose this answer in a few days.