Experience in stl use -- ensure that less < T > and operator < have the same semantics

As we all know, the stl container has a default sorting function. The default sorting less < T > is generally sorted by size. If we store custom elements and assume that we have multiple sorting factors, it mainly depends on how the operator < function in our custom class is written.

Let's start with an example.

#include <iostream>
#include <set> 
#include <algorithm> 
#include <iterator>
using namespace std;

class Widget{
	public:
	Widget(int a, int b) : m_index(a), m_sepeed(b)
	{
	}
	
	size_t index() const
	{
		return m_index;
	}
	
	size_t maxSpeed() const
	{
		return m_sepeed;
	}
	
	bool operator<(const Widget& rhs) const
	{
		return this->index() < rhs.index();
	}
	
private:
	int m_index;
	int m_sepeed;
};

void print(const Widget& w)
{
	cout << w.index() << " ";
}

int main()
{
	typedef set<Widget> SetVec;
	typedef set<Widget>::iterator SetIter;
	
	SetVec setW;
	
	for(int index = 10; index > 0; --index)
	{
		Widget w(index, (index * 5) / 3);
		setW.insert(w);
	}
	
	for_each(setW.begin(), setW.end(), print); // 1 2 3 4 5 6 7 8 9 10
	
	return 0;
}

In the above example, we customized the class Widget, which has two member variables, index and maxSpeed. The operator < function compares by index.

Therefore, when you execute the above code, you can see the results from small to large. But what if we want to store at maximum speed? At present, the existing structure can not meet our needs.

Suppose we need to create a container set < widget > sorted according to the maximum speed. We know that the default sorting function of the container uses less < widget >, and less < widget > is performed according to the operator < function by default.

So, can we specialize in less < widget > and cut off the connection between less < widget > and operator < functions. As follows:

template<>
struct std::less<Widget> : public binary_function<Widget, Widget, bool>
{
	bool operator()(const Widget& lhs, const Widget& rhs) const
	{
		return lhs.maxSpeed() > rhs.maxSpeed();
	}
};

The above code looks like a simple template specialization, but it specialized a template located in the std space. Generally speaking, the template in the std space is reserved for the standard library, not developers.

An attempt to compile the code failed. report errors:

[Error] specialization of 'template<class _Tp> struct std::less

Then modify the code:

namespace std{
	template<>
	struct less<Widget> : public binary_function<Widget, Widget, bool>
	{
		bool operator()(const Widget& lhs, const Widget& rhs) const
		{
			return lhs.maxSpeed() > rhs.maxSpeed();
		}
	};	
}

After modification, the code can run normally and can be sorted according to the maximum speed of the element Widget as expected.

In general, it is forbidden to modify components of std name namespace, which usually leads to undefined behavior, but this is allowed in some specific cases. Especially when programmers can customize the template in std for user-defined types.

In the above example, operator < < is not only the default implementation of less, but also the thing that users expect less to get, and let less do other things without calling operator <, which will go against the wishes of users for no reason.

What we often do is that we can specify a different type of function or function to replace the default sorting function. This is also an example that has been used in the previous association container to implement its own comparison function.

For the above example, the simplest implementation method is as follows:

struct MaxSpeedCompare : public binary_function<Widget, Widget, bool>
{
	bool operator()(const Widget& lhs, const Widget& rhs) const
	{
		return lhs.maxSpeed() > rhs.maxSpeed();
	}
};

In order to create a container with the maximum speed as the sorting factor, we can directly use MaxSpeedCompare as the comparison function type of the container.

typedef set<Widget, MaxSpeedCompare> SetVec;

SetVec setW;

Because everyone makes a comparison by calling the operator < function when using less by default, we should try our best to avoid modifying the behavior of less, because it may mislead others.

The solution is that you can customize your own implementation type to replace the default sorting method.

Tags: C++ less STL

Posted on Sun, 28 Nov 2021 16:20:34 -0500 by brian79