Sign in to follow this  

C++ struct has no default memberwise comparison for operator==?!

This topic is 3843 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Everything I've read says structs in C++ should have a default operator== defined automatically that uses memberwise comparison. However, VS2005 doesn't seem to think so. Simple sample code to illustrate issue:
struct testStruct {
	int data1;
	int data2;
	int data3;
};

int main(void) {
	testStruct test;
	testStruct test2;

	if (test == test2) {
		// do something
	}
	
	return 0;
}
Result: error C2676: binary '==' : 'testStruct' does not define this operator or a conversion to a type acceptable to the predefined operator If structs truly shouldn't have a default memberwise operator== implicitly supplied by the compiler, is there a way to define one explicitly that doesn't involve an incredibly long and annoying bit of code (along the lines of if ((member1 == rhs.member1) && (member2 == rhs.member2) && (member3 == rhs.member3) ... )) for a structure with several dozen members? Please tell me there's something very simple I'm missing, because if not, I have a few hundred lines of practically-meaningless code to write for several classes.

Share this post


Link to post
Share on other sites
Well actually I don't work with structs ever so I am not sure if you can do operator overloading there but if it was classes for example you could just overload the == operator for that class.

Share this post


Link to post
Share on other sites
Structs are just classes with default public access instead of default private.

I know I can overload the operator explicitly (as I mentioned), but I have about a dozen classes, each of which uses a data structure with upwards of 50 members. To define an overload of operator== would involve TONS of code. I was hoping for some method that didn't involve typing an entire novel just to implement comparison. Looks like I'm out of luck though. :(

Thanks.

Share this post


Link to post
Share on other sites
http://msdn2.microsoft.com/en-us/library/sah8k6f4(vs.80).aspx

That link suggests this idea:

struct E {
// operator int();
};

int main()
{

E e1, e2;
e1 == e2; // uncomment operator int in class E, then
// it is OK even though neither E::operator==(E) nor
// operator==(E, E) defined. Uses the conversion to int
// and then the builtin-operator==(int, int)
}

Not sure if that helps :-\

Share this post


Link to post
Share on other sites
Quote:
Original post by F1N1TY
http://msdn2.microsoft.com/en-us/library/sah8k6f4(vs.80).aspx

That link suggests this idea:

struct E {
// operator int();
};

int main()
{

E e1, e2;
e1 == e2; // uncomment operator int in class E, then
// it is OK even though neither E::operator==(E) nor
// operator==(E, E) defined. Uses the conversion to int
// and then the builtin-operator==(int, int)
}

Not sure if that helps :-\


Nope as this can only work in situations where there is only one member, in which case writing a real operator= is trivial.

Never use implicit operator functions.You will find that the compiler starts being very free with where it does such conversions, not always where you expect.

Share this post


Link to post
Share on other sites
Well, if that doesn't help, this conversation from GDNet IRC will:

<F1N1TY> you guys know anything about this: (Link to this forum)
<Zao> F1N1TY: Afair, there's no default op==
<Zao> Only the ctor, cctor, op= and dtor are automagic.

Share this post


Link to post
Share on other sites
Sadly, that isn't too helpful since it doesn't even show how E::int() is defined, just the declaration. And a structure that's several kilobytes long isn't going to fit in any built-in type, so I don't see how casting the structures is going to help me.

Thanks for trying, though. :)

Just for more background if anyone wonders why I have such large structures, I'm reading records from a database file, and some of the records are quite large. To compare one record to another to find out if it's equal requires every member of the structures be compared. It seems to me like such a simple method of comparison would be implicitly assumed. It may not always be correct, but it'd at least mean only overloading operator== in those cases instead of every case. I'm comparing actual data, not pointers or references, so memberwise comparison would work fine. (I'm storing strings in std::strings so == is defined for those too.)

Share this post


Link to post
Share on other sites
Normally you'd compare row keys, not row data.

Assuming I actually needed a data-comparison function (YAGNI!!!), there's a couple of options.

1) If you want your structures to be "POD", evil #define magic involving BOOST_PP_*.

2) If not-quite-"POD" is okay, having the subcomponents of a row structure automatically register themselves in a table of some sort that allows iteration over the elements both for loading database entries or performing operations on them instead of hardcoding everything about each row every time. Possibly using BOOST_PP_* magic again, but it's avoidable here.

3) Not-at-all-"POD", instead pretending C++ is a dynamic language by making row elements an array of boost::variant< std::string, int, ... > or similar (with view accessors for easy access and improved type checking if desired).

Share this post


Link to post
Share on other sites
If your structs are POD types (Plain Old Data), then you can safely and portably (is that even a word? [smile]) do a memcpy in your operator==(). If you're feeling really game you may be able to get away with doing this for non-POD types as well, but whether this will work or not depends on the types involved and your compiler.....and it is in no way portable.


struct testStruct {
int data1;
int data2;
int data3;

testStruct& operator=(const testStruct& rhs) {
memcpy(reinterpret_cast<char*>(this),
reinterpret_cast<const char*>(&rhs),
sizeof(testStruct));
}
};

int main(void) {
testStruct test;
testStruct test2;

if (test == test2) {
// do something
}

return 0;
}



EDIT: Umm.....I have no idea why I was thinking operator=() but typing operator==(). Replace memcpy with memcmp and the same hold though [rolleyes]

[Edited by - joanusdmentia on June 8, 2007 7:21:29 AM]

Share this post


Link to post
Share on other sites
Well, if you have huge structures, it's likely that there's a problem with your design.

If you think there isn't you could let them all inherit from a class that has == defined as a raw memory compare (search the standard library) between the structs, and make sure that the structs are tightly packed in memory (so no bytes in their memory block have uninitialized values).

It qualifies as an ugly hack though, and I'd much rather rethink my design than try something like it.

Share this post


Link to post
Share on other sites
My solutions would be:
  • Refactor your design (using a dynamic property system with std::map<std::string,boost::any> for instance).
  • Write an automatic code generator, if the layout of your structure is straightforward.
  • Write them by hand.

Share this post


Link to post
Share on other sites
If only it could be that simple. Isn't that kind of database, though. Think of it as a file that contains configuration data. There are 100 different configuration settings, each with various types of values. You want to see if one instance of a configuration file is using all the same settings as another instance of a different configuration file. Is there a method that doesn't involve 100 ifs or 99 &&'s? :)

Looking like no.

Edit: Or maybe yes. The memcpy method looks promising. The only types I'm using that aren't POD are std::string and std::bitset. I'll have to make sure they'll work with memcpy.

Edit2: And it isn't my design, it's a 20-year old BBS door game I'm reading the database files of. I can't really change the way the data is stored in the files. I could change the way I read the files; right now I just read the data directly into a struct of the same size and "shape" as the data in the files. Makes for easy reading of the sort:


CfigInterfaceFile.read(reinterpret_cast<char *>(&configData), sizeof(configDataStruct));


And for anyone that just had IP klaxons sound in their heads, I have the permission of the game's author to do this. :)

Edit3: You know, I pretty much KNOW std::string and std::bitset will work with memcpy since I'm loading strings and bitsets out of the files directly in memcpy-style fashion. I'll give that a shot.

Edit4: Err, I think you meant memcmp. I'm trying that now and will let you know. :) All the types I'm using should be fine with just comparing the bytes in memory directly.

[Edited by - Xentropy on June 8, 2007 5:25:26 AM]

Share this post


Link to post
Share on other sites
Can all the data be represented by strings?

Would a big array of strings, sort & compare not work?

Are you using this to see if there have been any configuration changes so you can write the changes out to the file/database/...?

Share this post


Link to post
Share on other sites
Quote:
Original post by MaulingMonkey
Quote:
Original post by Xentropy
If only it could be that simple. Isn't that kind of database, though. Think of it as a file that contains configuration data.


Ahh. I'd definately go for #3 in my edit, then.

(Actually, no, that's a lie -- I'd assume YAGNI!!!)


I wish I didn't need it. I hadn't implemented comparison until now specifically BECAUSE I didn't need it. And now I do, and wish needing it didn't mean a ton of code.

What I need to do right now is compare a given game's settings to the stock settings so I can just report the game is stock instead of spelling out all the edits.

I'll look into the boost options you mentioned and see if I can change my library to using one of those instead of structs. Even if it's just as much work, at least it'll be more productive work since it'll mean less work for any future additions/changes.

Share this post


Link to post
Share on other sites
If you're using text configuration files, you could probably just deal with the original std::string array/map for the most part. Something like:



#include <algorithm>
#include <map>
#include <string>
#include <boost/lexical_cast.hpp>
#include <iostream>
#include <cassert>

class configuration_file {
std::map< std::string /* name */, std::string /* value */ > data;
public:
configuration_file( const std::string& filename ) {
std::ifstream file( filename.c_str() );
for ( std::string line; std::getline( file, line ); ) {
std::string::size_type equals = line.find("=");
if ( equals == std::string::npos ) {
// error handling --- not a key = value pairing
}

// Key/Value finding:
std::string key = line.substr( 0, equals );
std::string value = line.substr( equals , std::string::npos );

// Whitespace stripping:
key = key.substr( key.find_first_not_of( " \t" ), key.find_last_not_of( " \t" ) );
value = value.substr( value.find_first_not_of( " \t" ), value.find_last_not_of( " \t" ) );

data[key] = value;
}
}
friend bool operator==( const configuration_file& lhs, const configuration_file& rhs ) {
if ( lhs.data.size() != rhs.data.size() ) return false;
return std::equal( lhs.data.begin(), lhs.data.end(), rhs.data.begin() );
}
friend bool operator!=( const configuration_file& lhs, const configuration_file& rhs ) {
return !(lhs==rhs);
}
template < typename T >
T get( const std::string& fieldname ) const {
return boost::lexical_cast<T>( data[fieldname] );
}
template < typename T >
void set( const std::string& fieldname, T value ) {
data[fieldname] = boost::lexical_cast< std::string >( value );
}
};

void example() {
configuration_file default_cfg( "default.cfg" );
configuration_file config( "configuration.cfg" );
if ( default_cfg != config ) {
std::cout << "Stop tamperin' with mah files!" << std::endl;
} else {
configuration.set<int>( "pineapple", 42 ); //he he tampering
}

assert( default_cfg.get<int>( "resolution_width" ) <= 800 );
assert( default_cfg.get<int>( "resolution_height" ) <= 600 );
}



Share this post


Link to post
Share on other sites
Quote:
Original post by MaulingMonkey
If you're using text configuration files


They're binary. Some fields are strings but it's stored in binary. And reverse-engineering the field locations and meanings was pretty fun. ;) Of course, the author sent me the source so I could verify everything later, but... The game was written in Pascal, too, so converting the Pascal-strings to C-strings and converting real48's to 64-bit doubles were additional challenges I already worked out long ago.

Share this post


Link to post
Share on other sites
Quote:
Original post by Xentropy
Quote:
Original post by MaulingMonkey
If you're using text configuration files


They're binary. Some fields are strings but it's stored in binary. And reverse-engineering the field locations and meanings was pretty fun. ;) Of course, the author sent me the source so I could verify everything later, but... The game was written in Pascal, too, so converting the Pascal-strings to C-strings and converting real48's to 64-bit doubles were additional challenges I already worked out long ago.


Hmm. Well, it looks like boost::variant already defines operator== if all it's types are EqualityComparable, so it'd be a very easy adoption. Just switching the load code, the get/set to boost::get<T>(data) and data=value; instead of lexical casting, and the various use of std::string as a value representation to a std::variant<std::string,double,...>, and I think that's it.

Share this post


Link to post
Share on other sites
Thanks to joanusdmentia for pointing me in the direction of direct memory comparison. Preliminary testing seems to indicate it works!


bool operator==(const configDataStruct &rhs) {
if (memcmp(this,&rhs,sizeof(*this)))
return false;
else return true;
}


I know it may not be the most portable thing in the world, but if the portability of this breaks, so will the portability of the way I'm reading the files in the first place.

Edit: And I'm still going to look into using boost::variant to improve portability. Big thanks to MaulingMonkey as well. :) And everyone else that contributed to this thread. Saves me a TON of work in the long run!

Share this post


Link to post
Share on other sites
Why not just keep track of when things change in the record as you modify it, instead of having to do massive data comparisions?
One bool that you keep updated would save a lot of hassle.

Also note that std::strings cannot be safely comapred with memcmp, nor can any of the stl containers, nor can char* strings.

Share this post


Link to post
Share on other sites
Quote:
Original post by Jerax
Why not just keep track of when things change in the record as you modify it, instead of having to do massive data comparisions?
One bool that you keep updated would save a lot of hassle.


Because I'm not the only one modifying the file. In fact, at the moment I don't write to the files at all, just read them. I have to see what the OTHER accessor has changed.

Quote:
Also note that std::strings cannot be safely comapred with memcmp, nor can any of the stl containers, nor can char* strings.


Perhaps not portably, but at least with VS2005's implementation, char arrays are comparing perfectly with memcmp, as is std::bitset. I'm as surprised as you are, but I tested a few different ways of creating std::bitsets that contained the same data (and another with different data) and memcmp was accurately reporting their sameness or lack thereof.

Share this post


Link to post
Share on other sites
I can't believe how much I mixed up operator=() and operator==() in my head in my last post. I need sleep.....

Anyway, you're probably lucking out because of the size of your strings/bitsets. A common optimisation is to keep a small array as part of the data structure to avoid the memory allocation for small data sets, I know std::string does this and wouldn't be surprised if std::bitset does aswell. If your strings and bitsets are always going to be under the size of this small buffer then you might be OK, but if not it'll all come crashing down around you once you pass that size (literally [smile]).

Share this post


Link to post
Share on other sites
Actually I had to modify the bitset header slightly to allow for 16-bit bitsets that actually allocated in 16 bits instead of 32. I'm only ever using 16 and 32 bit bitsets, but from testing bitsets as large as 256-bits, the VS2005 implementation seems to only include the bits themselves as a data member of a bitset. The rest of the class is all functions. So sizeof(bitset<T>) is always T/8 (rounded up to the nearest multiple of 4 by default, nearest multiple of 2 with my changing the base storage type to a short from a long), and if you just write a bitmap directly into a bitset<T> (or read it directly from) via a memcpy or binary file I/O, it works beautifully.

I don't actually read/write std::strings as memory maps. I know that wouldn't work. I load the data from the files into the proper sized char array and just convert the data to a std::string in the accessor method. I have to manipulate the data to use it anyway since it's stored in Pascal strings (first byte declares the size of the string to follow, rather than null-terminated strings like C uses).

Share this post


Link to post
Share on other sites
Quote:
Original post by Xentropy

bool operator==(const configDataStruct &rhs) {
if (memcmp(this,&rhs,sizeof(*this)))
return false;
else return true;
}


Shortcut:


bool operator==(const configDataStruct &rhs) {
return !memcmp(this,&rhs,sizeof(*this));
}

Share this post


Link to post
Share on other sites

This topic is 3843 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this