Efficient and simple comparison operators for structs - c++

The application I am working on currently has a large number structs which contain data which is input from various sources such as data bases and files. For example like this:
struct A
{
float val1;
std::string val2;
int val3;
bool operator < (const A& other) const;
};
For processing, these structs are stored up in STL-containers, such as maps and therefore need a comparison operator. These are all the same and using simple boolean logic they can be written like this:
bool A:operator < (const A& o) const {
return val1 < o.val1 ||
(val1 == o.val1 && ( val2 < o.val2 ||
(val2 == o.val2 && ( val3 < o.val3 ) ) );
}
This seems efficient, but has several drawbacks:
These expressions get huge if the structs as a dozen or more members.
It is cumbersome to write and maintain if members change.
It needs to be done for every struct separately.
Is there a more maintainable way to compare structs like this?

You can use the builtin comparison that ships with <tuple> like this:
#include <tuple>
bool A::operator < (const A& rhs) const {
return std::tie(val1, val2, val3) < std::tie(rhs.val1, rhs.val2, rhs.val3);
}
This doesn't scale when more and more data members are added to the struct, but this might also be a hint that you could create intermediate structs that implement operator < and hence play well with the above implementation of a top-level operator <.
Let me add three additional comments on operator <.
Once you have operator <, clients will expect that all other comparison operators are provided, too. Before we have the three-way comparison in C++20, you can avoid unnecessary boilerplate code by e.g. using the Boost operator library:
#include <boost/operators.hpp>
struct A : private boost::totally_ordered<A> { /* ... */ };
which generates all operators based on operator < and operator == for you.
In your example, there is no need for the operator to be a member of A. You can make it a free function, which is preferable (see here for the rationale).
If there is no intrinsic ordering related to A and you just need operator < to store instances as keys in a std::map, consider providing a named predicate.

Great answer by lubgr.
One further refinement I perform is the creation of a member function as_tuple on any object which is to be ordered by its members:
#include <string>
#include <tuple>
#include <iostream>
struct A
{
float val1;
std::string val2;
int val3;
// provide easy conversion to tuple
auto as_tuple() const
{
return std::tie(val1, val2, val3);
}
};
Which often gives rise to thoughts of a general system of making objects and tuples interchangeable in terms of comparisons
template<class T> auto as_tuple(T&& l) -> decltype(l.as_tuple())
{
return l.as_tuple();
}
template<class...Ts>
auto as_tuple(std::tuple<Ts...> const& tup)
-> decltype(auto)
{
return tup;
}
template<class L, class R>
auto operator < (L const& l, R const& r)
-> decltype(as_tuple(l), void(), as_tuple(r), void(), bool())
{
return as_tuple(l) < as_tuple(r);
}
Which allows such code as:
int main()
{
auto a = A { 1.1, "foo", 0 };
auto b = A { 1.1, "foo", 1 };
auto test1 = a < b;
std::cout << test1 << std::endl;
auto test2 = a < std::make_tuple(1.1, "bar", 0);
std::cout << test2 << std::endl;
auto test3 = std::make_tuple(1.0, "bar", 0) < std::make_tuple(1.1, "bar", 0);
std::cout << test3 << std::endl;
auto test4 = a < std::make_tuple(2l, std::string("bar"), 0);
std::cout << test4 << std::endl;
}
example: http://coliru.stacked-crooked.com/a/ead750f3f65e3ee9

Related

Is it possible to specify different C++ `operator==` functionality depending on the context at the call site?

I'm trying to make floating point equality comparisons explicit for my custom classes (exact comparison vs approximate comparison). I can avoid overloading the == operator and force users to call functions like exactly_equal or almost_equal instead but this doesn't work with std algorithms out of the box. I was wondering if there was a nice way to get the best of both worlds by forcing some explicit operator lookup at the call site (kind of like std::rel_ops).
For example, let's say I have this CustomType class and some operator== overloads:
struct CustomType {
double value;
};
namespace exactly_equal {
auto operator==(CustomType const& lhs, CustomType const& rhs) -> bool {
return lhs.value == rhs.value;
}
} // namespace exactly_equal
namespace almost_equal {
auto operator==(CustomType const& lhs, CustomType const& rhs) -> bool {
return std::abs(lhs.value - rhs.value) < 1e-2; // This isn't the "right" way, just an example.
}
} // namespace almost_equal
Using this class I can do something like this, which compiles and runs fine:
auto main() -> int {
auto const a = CustomType{1.0/3.0};
auto const b = CustomType{0.3333};
{
using namespace exactly_equal;
if (a == b) {
std::cout << "Exact!" << std::endl;
}
}
{
using namespace almost_equal;
if (a == b) {
std::cout << "Inexact!" << std::endl;
}
}
return 0;
}
The thing that doesn't work is the argument-dependent lookup when using std functions:
auto main() -> int {
auto const items = std::vector<CustomType>{{1.0/3.0}};
auto const value = CustomType{0.3333};
{
using namespace exactly_equal;
// error: no match for 'operator==' (operand types are 'const CustomType' and 'const CustomType')
if (std::find(items.begin(), items.end(), value) != items.end()) {
std::cout << "Exact!" << std::endl;
}
}
{
using namespace almost_equal;
// error: no match for 'operator==' (operand types are 'const CustomType' and 'const CustomType')
if (std::find(items.begin(), items.end(), value) != items.end()) {
std::cout << "Inexact!" << std::endl;
}
}
return 0;
}
Most suggestions for adding operators involve some sort of base class with operator overloads or a pattern similar to using namespace std::rel_ops (which also fails argument-dependent lookup). I am not sure a base class would help for this problem and ideally I would want to use this solution on classes I don't own (and can't modify).
I could use explicit functions and types for my data structures and algorithms:
struct ExactlyEqualPredicate{
auto operator()(CustomType const& lhs, CustomType const& rhs) const -> bool {
return lhs.value == rhs.value;
}
};
struct AlmostEqualComparator{
CustomType value;
auto operator()(CustomType const& other) const -> bool {
return std::abs(value.value == other.value) < 1e-2;
}
};
auto main() -> int {
auto const items = std::vector<CustomType>{{1.0/3.0}};
auto const value = CustomType{0.3333};
if (std::find_if(items.begin(), items.end(), AlmostEqualComparator{value}) != items.end()) {
std::cout << "Inexact!" << std::endl;
}
auto custom_map = std::unordered_map<CustomType,
std::string,
std::hash<CustomType>,
ExactlyEqualPredicate>{
{CustomType{0.3333}, "Exact!"},
};
if (auto iter = custom_map.find(value); iter != custom_map.end()) {
std::cout << iter->second << std::endl;
}
return 0;
}
but this becomes quite verbose and breaks down when I have nested containers (std::vector<std::vector<CustomType>>) or other complex structures. Template classes seem to have a couple extra rules for argument-dependent lookup but a nice way to accomplish this with templates was not immediately clear to me.
The goal is to keep the equality comparison explicit (exact comparison vs approximate comparison) at the call site without making usage of external functions and std algorithms overly difficult and/or verbose. Is there a solution I'm missing or some black magic I haven't thought of that might make this possible? I currently use C++17 in my codebase.
Thanks!
You have already enumerated the plausible, flawed approaches with the exception of wrapper types that define their own comparisons. They might store pointers or references to the underlying objects to avoid copying, although even that would not allow algorithms to interpret containers differently. For that, the modern solution is to use Ranges (perhaps as included in C++20) with its composable transformations and projections (much like in your last approach).

c++ auto-comparator for structs

There is many primitive structs (several hundreds), that are used to transfer data between two components (for example a player and a server). There are no methods in them, just raw data.
The task is to write all requests and answers to be able to replay a player scenario without a server (we remember all question and all answers, that are pure functions).
So the task is put this structs in map without comparator. Now we use memcmp, it allows not to think about changes in this structs and it is compact, but there are too many problems with padding and etc.
Is it possible to get smth like getHashValue or any default comparator with metaprogramming in c++?
Conditions:
1) I do not want to create a comparator for each struct.
2) I want to have an error if a field was added or deleted if it breaks existing behavior and needs fix.
3) I don't want to change header files with struct definitions.
Example of a struct
struct A {
int a;
int b;
c c;
}
bool operator<(const A& a1, const A& a2)
{
if (a1.a != a2.a) return a1.a < a2.a;
if (a1.b != a2.b) return a1.b < a2.b;
if (a1.c != a2.c) return a1.c < a2.c;
return false;
}
I can consider other languages to implement this exact part (collect questions/answers), if it will not require to describe all this structs on that language again.
In C++17 you can pull this off if you are willing to (A) hard code how many elements are in each struct somewhere, and (B) write or generate code for each count of number of elements in the struct.
template<std::size_t N>
using size_k = std::integral_constant<std::size_t, N>;
template<class T>
auto auto_tie( size_k<0>, T&& t ) {
return std::tie();
}
template<class T>
auto auto_tie( size_k<1>, T&& t ) {
auto& [ x0 ] = std::forward<T>(t);
return std::tie( x0 );
}
template<class T>
auto auto_tie( size_k<2>, T&& t ) {
auto& [ x0, x1 ] = std::forward<T>(t);
return std::tie( x0, x1 );
}
// etc
now, in the namespace of the struct in question, write
struct foo {
int x;
};
struct bar {
int a, b;
};
size_k<1> elems( foo const& ) { return {}; }
size_k<2> elems( bar const& ) { return {}; }
an elems function that return the size_k counting how many elements.
Now in the namespace of the structs, write:
template<class T, class Size=decltype(elems(std::declval<T const&>()))>
bool operator<( T const& lhs, T const& rhs ) {
return auto_tie( Size{}, lhs ) < auto_tie( Size{}, rhs );
}
and you are done.
Test code:
foo f0{1}, f1{2};
bar b0{1,2}, b1{-7, -3};
std::cout << (f0<f1) << (f1<f0) << (f0<f0) << "\n";
std::cout << (b0<b1) << (b1<b0) << (b0<b0) << "\n";
Live example.
Getting further than this will require writing 3rd party tools or waiting for reflection extension to C++, maybe in C++20 or 23.
If you get elems wrong, I believe the structured bindings code in auto_tie should generate an error.
I suppose you could write your own compare operator based upon memcmp.
bool operator<(const A &lhs, const A &rhs) {
return memcmp(&lhs, &rhs, sizeof(A)) < 0;
}
Off course, writing these for each object might be a burden, so you could write a template for this. Though without some SFINAE it will cover too much types.
#include <type_traits>
#include <cstring>
template<typename T>
std::enable_if_t<std::is_pod_v<std::decay_t<T> //< Check if POD
&& !std::is_fundamental_v<std::decay_t<T>>>, //< Don't override for fundamental types like 'int'
bool>
operator<(const T &lhs, const T &rhs) {
return memcmp(&lhs, &rhs, sizeof(std::decay_t<T>)) < 0;
}
EDIT: Note that this technique requires you to zero-initialize the structs.
Looks like the best way to do it is to write a generator, that will generate .h and .cpp with bool operator< for all types in this header file. Then add this project as pre-build step to the main.
It doesn't look like a nice solution, but it allows to avoid hand-written code duplication and will support adding/removing new structs/fields. So I didn't find a better way.

Struct member equality without overloading operator== in C++

Is it possible to define some kind of template that can create a generic comparable operator for structs?
For example is it possible for something like this?
struct A
{
int one;
int two;
int three;
};
bool AreEqual()
{
A a {1,2,3};
A b {1,2,3};
return ComparableStruct<A>(a) == ComparableStruct<A>(b);
}
All this does is a field by field comparison of the structs. You can assume all fields are of basic types or have overloaded operator==.
I have a lot of structs like this and it would save me a lot of time if I can just put it in a template or something for comparison rather than defining an operator== for every single struct. Thanks!
Update
It seems like this is not possible with C++. I wonder why this is voted out of C++ proposals, if anyone has the reasons let us know!
For solution that works with basic types only see solution by R Sahu.
Is it possible to define some kind of template that can create a generic comparable operator for structs?
If the struct has no padding, you can use:
template <typename T>
struct ComparableStruct
{
ComparableStruct(T const& a) : a_(a) {}
bool operator==(ComparableStruct const& rhs) const
{
return (std::memcmp(reinterpret_cast<char const*>(&a_), reinterpret_cast<char const*>(&rhs.a_), sizeof(T)) == 0);
}
T const& a_;
};
Better yet, you can use a function template.
template <typename T>
bool AreEqual(T cost& a, T const& b)
{
return (std::memcmp(reinterpret_cast<char const*>(&a), reinterpret_cast<char const*>(&b), sizeof(T)) == 0);
}
If the struct has any padding, there is no guarantee that use of std::memcmp will work to compare two objects.
Look at https://github.com/apolukhin/magic_get. This library can automagically generate comparison operators for some fairly simple structs.
#include <iostream>
#include <boost/pfr/flat/global_ops.hpp>
struct S {
char c;
int i;
double d;
};
int main() {
S s1{'a', 1, 100.500};
S s2 = s1;
S s3{'a', 2, 100.500};
std::cout << "s1 " << ((s1 == s2) ? "==" : "!=") << " s2\n";
std::cout << "s1 " << ((s1 == s3) ? "==" : "!=") << " s3\n";
}
// Produces
// s1 == s2
// s1 != s3
What you asking to do is to traverse through various struct and comparing members is my understanding.
Iterating over a struct
This seems that it can't be done with standard c++, but that thread gives some ideas on what libraries to use.
It's not clear from your question if all the structs have the same format or not, and I'm assuming they do not.

How to sort out the structure based on the result and show the output according to ranking?

I have just started learning C++ programming a month or so ago. I am having a great difficulty in ranking and printing out the output based on the ranking. I followed some of the ideas posted in the forum and my code is below. I have no idea of what I have missed and how the code works. What I am trying to do is to sort out the player_data[5] in ascending order based on the attempt field and then sort out again the player_data[5] with time elapsed where the order of the array is based on the attempt and then time elapsed if the attempt is the same. After I sort out the structure of array, I want to cout based on the ranking. Would someone tell me what I am missing and give a brief explanation on the code itself. TIA
#include <algorithm>
using namespace std;
bool player_sorter(player_score const& lhs,player_score const& rhs);
struct player_score
{
char name[31];
int num_attempt;
time_t time_elapsed;
} player_data[5];
bool player_sorter(player_score const& lhs, player_score const& rhs)
{
if (lhs.num_attempt != rhs.num_attempt)
return lhs.num_attempt < rhs.num_attempt;
if (lhs.time_elapsed != rhs.time_elapsed)
return lhs.time_elapsed < rhs.time_elapsed;
}
The std::sort functions works with standard containers, as far as I know it doesn't with C-style array. You should define your type:
typedef struct player_score
{
char name[31];
int num_attempt;
time_t time_elapsed;
} player_score;
And then declare the actual container of your data:
std::vector<player_score> player_data;
Once your filled your container you can sort it with
std::sort(player_data.begin(), player_data.end(), player_sorter);
The function sort is an implementation of a sorting algorithm, maybe a Quicksort. If you call it like this you are telling the function that you want to sort from the beginning to the end, the whole container. The third argument is the function that performs the < comparison and it is fundamental to determine if an element goes before or after the other.
Also, player_sorter must return a value even if the two player_score are equal, you should add a return false to the end of the function, because in that case the first operand is not strictly less than the second but it is equal. In the case the operator were <= you would return true.
std::sort provides "good enough" sorting speed and complexity for most cases. Here is an example using a different struct and std::sort and several ways to sort.
struct foo {
int a;
int *b = nullptr;
bool operator<(const foo & other) const {
return (a < other.a);
}
}
void printvec(const std::vector<foo> & vec) {
for ( foo & f : vec ) {
std::cout << f.a << "\t" << (void*)f.b;
if ( f.b ) {
std::cout << "\t" << *f.b;
}
std::cout << "\n";
}
}
bool sort_foos_on_b(const foo & a, const foo & b) {
// if both are nullptr
if ( (nullptr == a.b) && (nullptr == b.b) ) {
return false;
}
// if one is nullptr
if ( (nullptr == a.b) != (nullptr == b.b) ) {
return (nullptr != a.b);
}
return (*a.b) < (*b.b);
}
void bar() {
const constexpr size_t MAX_FOO = 20;
std::default_random_engine generator;
std::uniform_int_distribution<int> rng(0,MAX_FOO-1);
std::vector<foo> vec(MAX_FOO);
// initialize
for ( foo & f : vec ) {
f.a = rng(generator);
if ( f.a & 1 ) {
f.b = &f.a;
}
}
// uses foo::operator<()
std::sort(vec.begin(), vec.end());
printvec(vec);
// uses lambda
std::sort(vec.begin(), vec.end(), [](const foo & a, const foo & b) -> bool {
// sorting on pointer because why not
return (f.a < f.b);
});
printvec(vec);
// uses explicit sort function
std::sort(vec.begin(), vec.end(), sort_foos_on_b);
printvec(vec);
}
bool foo::operator<() const is used to sort on foo::a.
The lambda is used to sort based on the pointer value of foo::b.
The explicit sort function is used to sort based on the nullness of foo::b and, if both are non-null, then the value pointed to by foo::b.
You can see tradeoffs for each. Using operator< will provide a default method of sorting (not needing to specify a sorting function to std::sort). The lambda allows you to customize your sorting right where you're using it. The explicit sorting function would be used if you need to sort the same way from different locations (instead of repeating the same lambda everywhere).
disclaimer: I wrote above code snippet from memory and don't have access to a compiler at the very moment so it might not compile "as-is"

Sorting just two elements using STL

Quite often I have two variables foo1 and foo2 which are numeric types. They represent the bounds of something.
A user supplies values for them, but like a recalcitrant musician, not necessarily in the correct order!
So my code is littered with code like
if (foo2 < foo1){
std::swap(foo2, foo1);
}
Of course, this is an idiomatic sort with two elements not necessarily contiguous in memory. Which makes me wonder: is there a STL one-liner for this?
I suggest to take a step back and let the type system do the job for you: introduce a type like Bounds (or Interval) which takes care of the issue. Something like
template <typename T>
class Interval {
public:
Interval( T start, T end ) : m_start( start ), m_end( end ) {
if ( m_start > m_end ) {
std::swap( m_start, m_end );
}
}
const T &start() const { return m_start; }
const T &end() const { return m_end; }
private:
T m_start, m_end;
};
This not only centralizes the swap-to-sort code, it also helps asserting the correct order very early on so that you don't pass around two elements all the time, which means that you don't even need to check the order so often in the first place.
An alternative approach to avoid the issue is to express the boundaries as a pair of 'start value' and 'length' where the 'length' is an unsigned value.
No, but when you notice you wrote the same code twice it's time to write a function for it:
template<typename T, typename P = std::less<T>>
void swap_if(T& a, T& b, P p = P()) {
if (p(a, b)) {
using std::swap;
swap(a, b);
}
}
 
std::minmax returns pair of smallest and largest element. Which you can use with std::tie.
#include <algorithm>
#include <tuple>
#include <iostream>
int main()
{
int a = 7;
int b = 5;
std::tie(a, b) = std::minmax({a,b});
std::cout << a << " " << b; // output: 5 7
}
Note that this isn't the same as the if(a < b) std::swap(a,b); version. For example this doesn't work with move-only elements.
if the data type of your value that you're going to compare is not already in c++. You need to overload the comparison operators.
For example, if you want to compare foo1 and foo2
template <class T>
class Foo {
private:
int value; // value
public:
int GetValue() const {
return value;
}
};
bool operator<(const Foo& lhs, const Foo& rhs) {
return (lhs.GetValue() < rhs.GetValue());
}
If your value is some type of int, or double. Then you can use the std::list<>::sort member function.
For example:
std::list<int> integer_list;
int_list.push_back(1);
int_list.push_back(8);
int_list.push_back(9);
int_list.push_back(7);
int_list.sort();
for(std::list<int>::iterator list_iter = int_list.begin(); list_iter != int_list.end(); list_iter++)
{
std::cout<<*list_iter<<endl;
}