Implementing comparison operators via 'tuple' and 'tie', a good idea?
(Note: tuple
and tie
can be taken from Boost or C++11.)
When writing small structs with only two elements, I sometimes tend to choose a std::pair
, as all important stuff is already done for that datatype, like operator<
for strict-weak-ordering.
The downsides though are the pretty much useless variable names. Even if I myself created that typedef
, I won't remember 2 days later what first
and what second
exactly was, especially if they are both of the same type. This gets even worse for more than two members, as nesting pair
s pretty much sucks.
The other option for that is a tuple
, either from Boost or C++11, but that doesn't really look any nicer and clearer. So I go back to writing the structs myself, including any needed comparision operators.
Since especially the operator<
can be quite cumbersome, I thought of circumventing this whole mess by just relying on the operations defined for tuple
:
Example of operator<
, e.g. for strict-weak-ordering:
bool operator<(MyStruct const& lhs, MyStruct const& rhs){
return std::tie(lhs.one_member, lhs.another, lhs.yet_more) <
std::tie(rhs.one_member, rhs.another, rhs.yet_more);
}
(tie
makes a tuple
of T&
references from the passed arguments.)
Edit: The suggestion from @DeadMG to privately inherit from tuple
isn't a bad one, but it got quite some drawbacks:
- If the operators are free-standing (possibly friends), I need to inherit publicly
- With casting, my functions / operators (
operator=
specifically) can be easily bypassed - With the
tie
solution, I can leave out certain members if they don't matter for the ordering
Are there any drawbacks in this implementation that I need to consider?
Solution 1:
This is certainly going to make it easier to write a correct operator than rolling it yourself. I'd say only consider a different approach if profiling shows the comparison operation to be a time-consuming part of your application. Otherwise the ease of maintaining this should outweigh any possible performance concerns.
Solution 2:
I have come accross this same problem and my solution uses c++11 variadic templates. Here comes the code:
The .h part:
/***
* Generic lexicographical less than comparator written with variadic templates
* Usage:
* pass a list of arguments with the same type pair-wise, for intance
* lexiLessthan(3, 4, true, false, "hello", "world");
*/
bool lexiLessthan();
template<typename T, typename... Args>
bool lexiLessthan(const T &first, const T &second, Args... rest)
{
if (first != second)
{
return first < second;
}
else
{
return lexiLessthan(rest...);
}
}
And the .cpp for the base case without arguments:
bool lexiLessthan()
{
return false;
}
Now your example becomes:
return lexiLessthan(
lhs.one_member, rhs.one_member,
lhs.another, rhs.another,
lhs.yet_more, rhs.yet_more
);
Solution 3:
In my opinion, you're still not addressing the same issue as the std::tuple
solves- namely, you have to know both how many and the name of each member variable, you're duplicating it twice in the function. You could opt for private
inheritance.
struct somestruct : private std::tuple<...> {
T& GetSomeVariable() { ... }
// etc
};
This approach is a little bit more of a mess to begin with, but you're only maintaining the variables and names in one place, instead of in every place for every operator you wish to overload.
Solution 4:
If you plan to use more than one operator overload, or more methods from tuple, I'd recommend making tuple a member of the class or derive from tuple. Otherwise, what you're doing is a lot more work. When deciding between the two, an important question to answer is: Do you want your class to be a tuple? If not I would recommend containing a tuple and limiting the interface by using delegation.
You could create accessors to "rename" the members of the tuple.