Skip to content

Instantly share code, notes, and snippets.

@dirvine
Last active December 19, 2015 07:49
Show Gist options
  • Save dirvine/5921152 to your computer and use it in GitHub Desktop.
Save dirvine/5921152 to your computer and use it in GitHub Desktop.
FullyOrdered c++11 class (assume that will work in msvc as well as gcc clang)
#ifndef MY_CLASS_H
#define MY_CLASS_H
#include <tuple>
#include <utility>
template <typename T, typename U>
struct MyClass {
T first;
U second;
// semiregular
MyClass(const MyClass& other)
: first(other.first),
second(other.second)
{}
MyClass(MyClass&& other)
: MyClass() {
swap(*this, other);
}
MyClass()
: first(),
second()
{}
~MyClass()
{}
MyClass& operator=(MyClass other) {
swap(*this, other);
return *this;
}
// regular
friend
bool operator==(const MyClass& lhs, const MyClass& rhs) {
return std::tie(lhs.first, lhs.second)
== std::tie(rhs.first, rhs.second);
}
friend
bool operator!=(const MyClass& lhs, const MyClass& rhs) {
return !operator==(lhs, rhs);
}
// fully ordered
friend
bool operator<(const MyClass& lhs, const MyClass& rhs) {
return std::tie(lhs.first, lhs.second)
< std::tie(rhs.first, rhs.second);
}
friend
bool operator>(const MyClass& lhs, const MyClass& rhs) {
return operator<(rhs, lhs);
}
friend
bool operator<=(const MyClass& lhs, const MyClass& rhs) {
return !operator>(lhs, rhs);
}
friend
bool operator>=(const MyClass& lhs, const MyClass& rhs) {
return !operator<(lhs, rhs);
}
};
// swap
void swap(MyClass& lhs, MyClass& rhs) /* noexcept */ {
using std::swap;
swap(lhs.first, rhs.first);
swap(lhs.second, rhs.second);
}
#endif // MY_CLASS_H
@dirvine
Copy link
Author

dirvine commented Jul 13, 2013

  • void swap(MyClass& lhs, MyClass& rhs) noexcept - I'd make this a non-member function, i.e. defined outside the class. If it needs access to private members (it doesn't in this case) I'd make it a friend function, which means the definition can stay inside the class and it is still not a member. (noexcept won't fly on MSVC 11 either)
    Agreed
  • #include <utility> for std::swap
    Agreed
  • Second constructor should be MyClass(MyClass&& other)
    Agreed
  • Not sure if there should be a default constructor, since it forces a requirement for a default constructor for each of types T and U not yet convinced

Why would this be a bad thing, can you come up with cases where not forcing default construction is bad

  • MyClass& operator=(const MyClass& other) should be MyClass& operator=(MyClass other) to get the benefits of copy-and-swap and give the strong exception safety guarantee
    Agreed
  • I'd make all the overloaded operators non-member, non-friends in this case. If they had to be friends, I'd leave it as is. not yet convinced

Is there and argument for not including all the items relevant to the object in the object itself, I am aware Herb Sutter and Scott Meyers advocate preferring non member non friend methods, but is there a good explanation of why?

@Fraser999
Copy link

Why would this be a bad thing, can you come up with cases where not forcing default construction is bad

Well, not bad as such. But any function that's not required shouldn't exist in my opinion. Also, if a default-constructed class isn't immediately usable, it probably should be default-constructible.

I guess an example would be where we need to pass a path in the constructor (e.g. leveldb).

A big downside is that it limits its use in STL containers. I think that's why we buckled in making NonEmptyString default-constructible.

Is there and argument for not including all the items relevant to the object in the object itself, I am aware Herb Sutter and Scott Meyers advocate preferring non member non friend methods, but is there a good explanation of why?

Making them members increases coupling. A member function has access to all the private members of that class (not an issue here) which is undesirable if the function doesn't need access to them. The class ends up smaller with less members.

Also, now that we're using templates much more, having classes with as few member functions as possible helps a lot I think. We've hit a few places where we've had to implement no-op member functions just to let the class in question "fit". I think it's better (less clutter) to have a templated non-member function that is a no-op by default, and specialise it for the classes as needed.

@dirvine
Copy link
Author

dirvine commented Jul 13, 2013

I guess an example would be where we need to pass a path in the constructor (e.g. leveldb).

Are we then saying non compile time creatable objects may not be default constructable ? Does this then lead to compile time fully established objects should be?

The class ends up smaller with less members.
I have been watching [Stepanov](http://www.youtube.com/playlist?list=PLHxtyCq_WDLXryyw91lahwdtpZsmo4BGD) and it's interesting he favours everything in a class related to that class, apparently regardless of whether it couples or not. I am not sure who is right here, I did read the Dr dobbs thing a wile back and was partially convinced, but I am still not sure who is correct. If a class is able to be broken down to smaller classes then fair enough we treat it as a collection of possibilities, but then again if it's concrete then should it not be fully encapsulated ?

This seems to be a fundamental difference with Stepanov and the others, I need to get more information I think. Perhaps it's where the members/non members are obvious (== < begin etc.) that the argument about removing form the class becomes more powerful. I think for specifics to the class then members are perhaps preferable, then again what is obvious ? The obvious methods are close to potentially 'keywords' I imagine. Does this make sense, make obvious stuff non member where possible, but closely related functions, members, ie members where the name is not something that's common or we need time to think about.

@Fraser999
Copy link

Are we then saying non compile time creatable objects may not be default constructable ? Does this then lead to compile time fully established objects should be?

Not really sure about that - I guess we see this differently :-) My thought process is more like: Do we need this function (in this case, c'tor)? If so, implement it. If not, should we have this function? This is the trickier one to answer. It could be "yes" if, e.g. we really need to have resize called on a vector of these. Or if we're talking about operator== we probably should implement != too. But I generally try to avoid implementing things we're not sure that we'll need.

As for Stepanov's arguments, I'm planning to watch the video soon. But so far, for me the Meyers/Sutter/Alexandrescu arguments are more persuasive. They provide a couple of tangible reasons why non-member functions are preferable, whereas Stepanov's seem to be more like "style" preferences.

Either way, this is probably something that should be a documented policy in our style guide. I'd vote for consistency in approach here.

@dirvine
Copy link
Author

dirvine commented Jul 13, 2013

I think then from the above example if we need any part of semiregular, regular or fullyordered then we should implement all of that section at least. The question then becomes what type of object we are creating and why.

We just need to refine rules for member/nonmember issues and I think this looks like it's based on friend requirements or not.

@ned14
Copy link

ned14 commented Jun 23, 2014

There is a standard definition of a standard type in c++. Read https://github.com/boostcon/cppnow_presentations_2014/blob/master/files/cxx11-library-design.pdf?raw=true around page 35 onwards. Note the useful concept check test for a CI or static analyser to automate for you. As a summary, you need more noexcept and no swap which is now usually redundant.

@ned14
Copy link

ned14 commented Jun 23, 2014

Oh and you're missing hash. Everyone always forgets hash, me as well.

@dirvine
Copy link
Author

dirvine commented Jun 23, 2014

Yes hash is important now and will be added. Good catch Niall. The linked presentation is very good. So here goes an hours study again :-) 👍

@dirvine
Copy link
Author

dirvine commented Jun 23, 2014

I am not sure, but perhaps best to keep swap as a friend of the class defined in the class ? I need to check but friend has altered in c++11 to be more of a route along ADL as opposed to a coupling mechanism. I prefer to have all the typical class members/features defined in place, I know Fraser prefers the opposite.

We would probably all prefer msvc played nice and we could just use = default = delete as well as ignore swap definitions like this at all as you say Niall.

In any case I think hash need to be a non member and specialisation of std::hash? is that right?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment