I'm trying to implement strict weak ordering in a subclass that I want to place in an STL set container. The STL set uses operator< to order its elements in strict weak ordering. In my case I have a hierarchy of types and I am not quite sure how to achieve this for derived types. To this end, I put together a quick live demo showing where I am uncertain. I am ordering the fields using an easy to use std::tie technique. My area of uncertainty is how I should call the superclass' operator< prior to calling the std::tie comparison on the derived fields.
struct Base {
Base(const int& rIntVal, const std::string& rStrVal)
: mIntVal(rIntVal)
, mStrVal(rStrVal)
{}
inline bool operator<(const Base& rhs) const {
return std::tie(mIntVal, mStrVal) < std::tie(rhs.mIntVal, rhs.mStrVal);
}
private:
int mIntVal;
std::string mStrVal;
};
struct Derived : public Base {
Derived(
const int& rIntVal,
const std::string& rStrVal,
const std::string& rOtherStrVal,
const std::string& rOtherStrVal1)
: Base(rIntVal, rStrVal)
, mOtherStrVal(rOtherStrVal)
, mOtherStrVal1(rOtherStrVal1)
{}
inline bool operator<(const Derived& rhs) const {
// not sure what to do here - this is my best guess???
if( Base::operator<(rhs) ) {
return std::tie(mOtherStrVal, mOtherStrVal1) <
std::tie(rhs.mOtherStrVal, rhs.mOtherStrVal1);
} else {
return false;
}
}
private:
std::string mOtherStrVal;
std::string mOtherStrVal1;
};
You would do best to tie a reference to the base class:
bool operator<(const Derived& rhs) const {
return std::tie(static_cast<const Base&>(*this), mOtherStrVal, mOtherStrVal1) <
std::tie(static_cast<const Base&>(rhs), rhs.mOtherStrVal, rhs.mOtherStrVal1);
}
This will compare by the superclass fields first and then by the subclass fields.
Firstly, you could choose to have the derived fields take priority over the base ones, so that the derived members are considered first, or you could give the base fields priority. Which to do depends on the meaning of your class and how it should be sorted.
You have chosen to compare the base fields first, which is fine, so we'll go with that.
To be a strict weak ordering you should only compare the derived fields when the base sub-objects are equivalent (i.e. neither is less-than the other).
With your current code if you have lhs.mIntVal < rhs.mIntVal you should return true, but instead you go on to compare the derived fields, and might end up saying that lhs is not less than rhs even though the the result from the base class says it is.
So to make the result correct you need something equivalent to:
bool operator<(const Derived& rhs) const {
if (Base::operator<(rhs))
return true;
else if (rhs.Base::operator<(*this))
return false;
// base parts are equivalent, compare derived parts:
return std::tie(mOtherStrVal, mOtherStrVal1) <
std::tie(rhs.mOtherStrVal, rhs.mOtherStrVal1);
}
This is logically correct, but sub-optimal because you call Base::operator< twice. You could avoid that by including the base objects in the tie expression as ecatmur shows.
Related
I'm working with a C++ project and I need to do some assignment code to assign one object to another with a different type like this:
MyClass1 o1;
MyClass2 o2;
o2 = o1;
Ofc, we can make this work with the help of a copy assignment operator of MyClass2: MyClass2& operator=(const MyClass1&).
But this gonna be a very heavy job for me because there has been thousands of classes, which need to do the assignment like o2 = o1. I don't want to add a copy assignment operator for each of them one by one...
I'm thinking if there is some other way, such as some TMP method to help me...
I can ensure that MyClass1 and MyClass2 have exactly the same data members with the same declaration order (see below). If so, is there some TMP method, which could help me?
struct MyClass1 {
int a;
char ch;
std::string msg;
// some virtual member functions
};
struct MyClass2 {
int a;
char ch;
std::string msg;
// some virtual member functions
};
BTW, you may want to ask why there are such two classes/structs with the same data members. Well, this is about some historical reason, I can't fusion them onto one class/struct.
UPDATE
It seems that I didn't make my question clear. I'll make an example here.
void doJob(const MyClass1& o1) {}
void func1(MyClass1 o1) {
doJob(o1);
}
void func2(MyClass2 o2) {
MyClass o1;
o1.? = o2.?; // assign each element of o2 to o1.
doJob(o1);
}
Here is the real case. As you see, o1.? = o2.? contains multi lines, it depends on how many data members of MyClass1/MyClass2. I'm trying to find some way to avoid this stupid assignment of all data members one by one in the func2.
Also, as I said, I have thousands of classes like MyClass1/MyClass2, meaning that these classes have totally different data members.
So for MyClass1 and MyClass2, o1.? = o2.? is o1.a = o2.a; o1.ch = o2.ch; o1.msg = o2.msg; But for other classes, it may become o1.f = o2.f; o1.vec = o2.vec;. That's why I'm thinking I may need some TMP technique...
UPDATE2
Alice developed the classes:
struct MyClass1 {// data members};
struct MyClass2 {// data members};
// MyClass1 and MyClass2 have exactly the same data members and declaration order
struct MyClass3 {// data members};
struct MyClass4 {// data members};
// MyClass3 and MyClass4 have exactly the same data members and declaration order
...
...
struct MyClass1000 {// data members};
struct MyClass1001 {// data members};
// MyClass1000 and MyClass1001 have exactly the same data members and declaration order
I'm developing the functions:
void doJob1(const MyClass1& o1) {}
void func1(MyClass1 o1) {
doJob(o1);
}
void func2(MyClass2 o2) {
MyClass1 o1;
o1.? = o2.?; // assign each element of o2 to o1.
doJob1(o1);
}
...
...
void doJob1000(const MyClass1000& o1) {}
void func1000(MyClass1000 o1) {
doJob1000(o1);
}
void func1001(MyClass1001 o2) {
MyClass1000 o1;
o1.? = o2.?; // assign each element of o2 to o1.
doJob1000(o1);
}
Why did Alice do such a stupid design? For some historical reason...
Why not using std::memcpy? Because these classes contain virtual functions.
yes, you can... but I is not recommended unless the two classes have an exact one to one correspondence and the exact same meaning in the domain of your problem.
Why is not recommended?
Because the operation needs to be manually implemented (the compiler cannot help with a = default declaration).
auto operator=(MyClass1 const& other) -> MyClass2& {
std::tie(a, ch, msg) = std::tie(other.a, other.ch, other.msg);
return *this;
}
Because if you defined assignment, you will eventually need to define equality (==), and inequality (!=) and in both directions. Otherwise the clases will not behave logically.
bool operator==(MyClass1 const& mc1, MyClass2 const& mc2) {
return std::tie(mc1.a, mc1.ch, mc1.msg) == std::tie(mc2.a, mc2.ch, mc2.msg);
}
bool operator==(MyClass2 const& mc2, MyClass1 const& mc1) {
return std::tie(mc1.a, mc1.ch, mc1.msg) == std::tie(mc2.a, mc2.ch, mc2.msg);
}
bool operator!=(MyClass1 const& mc1, MyClass2 const& mc2) {
return std::tie(mc1.a, mc1.ch, mc1.msg) != std::tie(mc2.a, mc2.ch, mc2.msg);
}
bool operator!=(MyClass2 const& mc2, MyClass1 const& mc1) {
return std::tie(mc1.a, mc1.ch, mc1.msg) != std::tie(mc2.a, mc2.ch, mc2.msg);
}
Because if you define assignment, you will to define a constructor or a conversion from one to the other.
/*explicit?*/ operator MyClass1() const& {return {a, ch, msg};} // need thios
and a move constructor?
/*explicit?*/ operator MyClass1() && {return {a, ch, std::move(msg)};} // need this
if one can be ordered the other also, and you will need to define order between the two classes, in both directions
// won't even try
// bool operator<
// bool operator<=
// bool operator>
// bool operator>=
// bool operator<
// bool operator<=
// bool operator>
// bool operator>=
and for that matter any function that work with one should work with the other, because well, when you assign you are saying that two things are logical equal among other tings.
full code here: https://godbolt.org/z/c8d34eT48
While it seems to be your case (albeit a very suspicious case), as you see, you open a Pandora's box by defining equality between two classes.
Just by calling the "assignment" convert instead you save your self a big headache. Don't use operator=. My recommended code is to just use another name:
MyClass2& convert(MyClass1 const& from, MyClass2& to) {
std::tie(to.a, to.ch, to.msg) = std::tie(from.a, from.ch, from.msg);
return to;
}
MyClass2& convert(MyClass1&& from, MyClass2& to) {
std::tie(to.a, to.ch, to.msg) = std::tie(from.a, from.ch, std::move(from.msg));
return to;
}
More material: https://www.youtube.com/watch?v=ABkxMSbejZI
Once you understand the drawbacks, std::tie can help you (as shown).
Also, if all classes are public and simple, Boost.PFR https://www.boost.org/doc/libs/1_78_0/doc/html/boost_pfr.html
struct iClass
{
int a;
char ch;
std::string msg;
// implement iClass == = operator
};
struct MyClass1 : virtual iClass{
// some virtual member functions
};
struct MyClass2 : virtual iClass {
// some virtual member functions
};
Should be able to compare by reintrepret_cast to iClass as well assignment.
I have code that is somewhere between c++17 and c++20. Specifically, we have c++20 enabled on GCC-9 and clang-9, where it is only partially implemented.
In code we have quite big hierarchy of polymorphic types like this:
struct Identifier {
virtual bool operator==(const Identifier&other) const = 0;
};
struct UserIdentifier : public Identifier {
int userId =0;
bool operator==(const Identifier&other) const override {
const UserIdentifier *otherUser = dynamic_cast<const UserIdentifier*>(&other);
return otherUser && otherUser->userId == userId;
}
};
struct MachineIdentifier : public Identifier {
int machineId =0;
bool operator==(const Identifier&other) const override {
const MachineIdentifier *otherMachine = dynamic_cast<const MachineIdentifier*>(&other);
return otherMachine && otherMachine->machineId == machineId;
}
};
int main() {
UserIdentifier user;
MachineIdentifier machine;
return user==machine? 1: 0;
}
https://godbolt.org/z/er4fsK
We are now migrating to GCC-10 and clang-10, but because of reasons we still need to work on versions 9 (well, at least clang-9 as this is what android NDK currently has).
The above code stops compiling because new rules about comparison operators are implemented. Reversible operator== causes ambiguities. I can't use a spaceship operator because it is not implemented in versions 9. But I omitted this from the example - I assume that whatever works with == will work with other operators.
So:
What is the recommended approach to implementing comparison operators in c++20 with polymorphic types?
As an intermediate solution you could re-factor your polymorphic equality operator== to a non-virtual operator== defined in the base class, which polymorphically dispatches to a non-operator virtual member function:
struct Identifier {
bool operator==(const Identifier& other) const {
return isEqual(other);
}
private:
virtual bool isEqual(const Identifier& other) const = 0;
};
// Note: do not derive this class further (less dyncasts may logically fail).
struct UserIdentifier final : public Identifier {
int userId = 0;
private:
virtual bool isEqual(const Identifier& other) const override {
const UserIdentifier *otherUser = dynamic_cast<const UserIdentifier*>(&other);
return otherUser && otherUser->userId == userId;
}
};
// Note: do not derive this class further (less dyncasts may logically fail).
struct MachineIdentifier final : public Identifier {
int machineId = 0;
private:
virtual bool isEqual(const Identifier& other) const override {
const MachineIdentifier *otherMachine = dynamic_cast<const MachineIdentifier*>(&other);
return otherMachine && otherMachine->machineId == machineId;
}
};
There will now no longer be an ambiguity as dispatch on the isEqual virtual member function will always be done on the left hand side argument to operator==.
const bool result = (user == machine); // user.isEqual(machine);
OK, I see it wasn't mentioned in the answer given by #dfrib, so I'll extend that answer to show it.
You could add an abstract (pure virtual) function in the Identifier structure, which returns its "identity".
Then, in each structure which extends the Identifier structure, you could call that function instead of dynamically casting the input object and check that its type is matching the this object.
Of course, you will have to ensure complete distinguish between the set of identities of each structure. In other words, any two sets of identities must not share any common values (i.e., the two sets must be disjoint).
This will allow you to completely get rid of RTTI, which is pretty much the complete opposite of polymorphism IMO, and also yields an additional runtime impact on top of that.
Here is the extension of that answer:
struct Identifier {
bool operator==(const Identifier& other) const {
return getVal() == other.getVal();
}
private:
virtual int getVal() const = 0;
};
struct UserIdentifier : public Identifier {
private:
int userId = 0;
virtual int getVal() const override {
return userId;
}
};
struct MachineIdentifier : public Identifier {
private:
int machineId = 100;
virtual int getVal() const override {
return machineId;
}
};
If you want to support a structure with identifiers other some type other than int, then you can extend this solution to use templates.
Alternatively to enforcing a different set of identities for each structure, you could add a type field, and ensure that only this field is unique across different structures.
In essence, those types would be the equivalent of the dynamic_cast check, which compares between the pointer of the input object's V-table and the pointer of the input structure's V-table (hence my opinion of this approach being the complete opposite of polymorphism).
Here is the revised answer:
struct Identifier {
bool operator==(const Identifier& other) const {
return getType() == other.getType() && getVal() == other.getVal();
}
private:
virtual int getType() const = 0;
virtual int getVal() const = 0;
};
struct UserIdentifier : public Identifier {
private:
int userId = 0;
virtual int getType() const override {
return 1;
virtual int getVal() const override {
return userId;
}
};
struct MachineIdentifier : public Identifier {
private:
int machineId = 0;
virtual int getType() const override {
return 2;
virtual int getVal() const override {
return machineId;
}
};
This does not look like a problem of polymorphism. Actually, I think that there is any polymorphism at all is a symptom of a data model error.
If you have values that identify machines, and values that identify users, and these identifiers are not interchangeable¹, they should not share a supertype. The property of "being an identifier" is a fact about how the type is used in the data model to identify values of another type. A MachineIdentifier is an identifier because it identifies a machine; a UserIdentifier is an identifier because it identifies a user. But an Identifier is in fact not an identifier, because it doesn't identify anything! It is a broken abstraction.
A more intuitive way to put this might be: the type is the only thing that makes an identifier meaningful. You cannot do anything with a bare Identifier, unless you first downcast it to MachineIdentifier or UserIdentifier. So having an Identifier class is most likely wrong, and comparing a MachineIdentifier to a UserIdentifier is a type error that should be detected by the compiler.
It seems to me the most likely reason Identifier exists is because someone realized that there was common code between MachineIdentifier and UserIdentifier, and leapt to the conclusion that the common behavior should be extracted to an Identifier base type, with the specific types inheriting from it. This is an understandable mistake for anyone who has learned in school that "inheritance enables code reuse" and has not yet realized that there are other kinds of code reuse.
What should they have written instead? How about a template? Template instantiations are not subtypes of the template or of each other. If you have types Machine and User that these identifiers represent, you might try writing a template Identifier struct and specializing it, instead of subclassing it:
template <typename T>
struct Identifier {};
template <>
struct Identifier<User> {
int userId = 0;
bool operator==(const Identifier<User> &other) const {
return other.userId == userId;
}
};
template <>
struct Identifier<Machine> {
int machineId = 0;
bool operator==(const Identifier<Machine> &other) const {
return other.machineId == machineId;
}
};
This probably makes the most sense when you can move all the data and behavior into the template and thus not need to specialize. Otherwise, this is not necessarily the best option because you cannot specify that Identifier instantiations must implement operator==. I think there may be a way to achieve that, or something similar, using C++20 concepts, but instead, let's combine templates with inheritance to get some advantages of both:
template <typename Id>
struct Identifier {
virtual bool operator==(const Id &other) const = 0;
};
struct UserIdentifier : public Identifier<UserIdentifier> {
int userId = 0;
bool operator==(const UserIdentifier &other) const override {
return other.userId == userId;
}
};
struct MachineIdentifier : public Identifier<MachineIdentifier> {
int machineId = 0;
bool operator==(const MachineIdentifier &other) const override {
return other.machineId == machineId;
}
};
Now, comparing a MachineIdentifier to a UserIdentifier is a compile time error.
This technique is called the curiously recurring template pattern (also see crtp). It is somewhat baffling when you first come across it, but what it gives you is the ability to refer to the specific subclass type in the superclass (in this example, as Id). It might also be a good option for you because, compared to most other options, it requires relatively few changes to code that already correctly uses MachineIdentifier and UserIdentifier.
¹ If the identifiers are interchangeable, then most of this answer (and most of the other answers) probably does not apply. But if that is the case, it should also be possible to compare them without downcasting.
You don't have any polymorphism in your code. You can force a dynamic binding of the comparison operator function (polymorphism) by using either Identifier pointers or references.
For example, instead of
UserIdentifier user;
MachineIdentifier machine;
return user==machine? 1: 0;
With references you could do:
UserIdentifier user;
MachineIdentifier machine;
Identifier &iUser = user;
return iUser == machine ? 1: 0;
Conversely, you can explicitly call UserIdentifier's comparison operator:
return user.operator==(machine) ? 1: 0;
My problem comes from a project that I'm supposed to finish. I have to create an std::unordered_map<T, unsigned int> where T is a pointer to a base, polymorphic class. After a while, I figured that it will also be a good practice to use an std::unique_ptr<T> as a key, since my map is meant to own the objects. Let me introduce some backstory:
Consider class hierarchy with polymorphic sell_obj as a base class. book and table inheriting from that class. We now know that we need to create a std::unordered_map<std::unique_ptr<sell_obj*>, unsigned int>. Therefore, erasing a pair from that map will automatically free the memory pointed by key. The whole idea is to have keys pointing to books/tables and value of those keys will represent the amount of that product that our shop contains.
As we are dealing with std::unordered_map, we should specify hashes for all three classes. To simplify things, I specified them in main like this:
namespace std{
template <> struct hash<book>{
size_t operator()(const book& b) const
{
return 1; // simplified
}
};
template <> struct hash<table>{
size_t operator()(const table& b) const
{
return 2; // simplified
}
};
// The standard provides a specilization so that std::hash<unique_ptr<T>> is the same as std::hash<T*>.
template <> struct hash<sell_obj*>{
size_t operator()(const sell_obj *s) const
{
const book *b_p = dynamic_cast<const book*>(s);
if(b_p != nullptr) return std::hash<book>()(*b_p);
else{
const table *t_p = static_cast<const table*>(s);
return std::hash<table>()(*t_p);
}
}
};
}
Now let's look at implementation of the map. We have a class called Shop which looks like this:
#include "sell_obj.h"
#include "book.h"
#include "table.h"
#include <unordered_map>
#include <memory>
class Shop
{
public:
Shop();
void add_sell_obj(sell_obj&);
void remove_sell_obj(sell_obj&);
private:
std::unordered_map<std::unique_ptr<sell_obj>, unsigned int> storeroom;
};
and implementation of two, crucial functions:
void Shop::add_sell_obj(sell_obj& s_o)
{
std::unique_ptr<sell_obj> n_ptr(&s_o);
storeroom[std::move(n_ptr)]++;
}
void Shop::remove_sell_obj(sell_obj& s_o)
{
std::unique_ptr<sell_obj> n_ptr(&s_o);
auto target = storeroom.find(std::move(n_ptr));
if(target != storeroom.end() && target->second > 0) target->second--;
}
in my main I try to run the following code:
int main()
{
book *b1 = new book("foo", "bar", 10);
sell_obj *ptr = b1;
Shop S_H;
S_H.add_sell_obj(*ptr); // works fine I guess
S_H.remove_sell_obj(*ptr); // usually (not always) crashes [SIGSEGV]
return 0;
}
my question is - where does my logic fail? I heard that it's fine to use std::unique_ptr in STL containters since C++11. What's causing the crash? Debugger does not provide any information besides the crash occurance.
If more information about the project will be needed, please point it out. Thank you for reading
There are quite a few problems with logic in the question. First of all:
Consider class hierarchy with polymorphic sell_obj as base class. book and table inheriting from that class. We now know that we need to create a std::unordered_map<std::unique_ptr<sell_obj*>, unsigned int>.
In such cases std::unique_ptr<sell_obj*> is not what we would want. We would want std::unique_ptr<sell_obj>. Without the *. std::unique_ptr is already "a pointer".
As we are dealing with std::unordered_map, we should specify hashes for all three classes. To simplify things, I specified them in main like this: [...]
This is also quite of an undesired approach. This would require changing that part of the code every time we add another subclass in the hierarchy. It would be best to delegate the hashing (and comparing) polymorphically to avoid such problems, exactly as #1201programalarm suggested.
[...] implementation of two, crucial functions:
void Shop::add_sell_obj(sell_obj& s_o)
{
std::unique_ptr<sell_obj> n_ptr(&s_o);
storeroom[std::move(n_ptr)]++;
}
void Shop::remove_sell_obj(sell_obj& s_o)
{
std::unique_ptr<sell_obj> n_ptr(&s_o);
auto target = storeroom.find(std::move(n_ptr));
if(target != storeroom.end() && target->second > 0) target->second--;
}
This is wrong for couple of reasons. First of all, taking an argument by non-const reference suggest modification of the object. Second of all, the creation of n_ptr from a pointer obtained by using & on an argumnet is incredibly risky. It assumes that the object is allocated on the heap and it is unowned. A situation that generally should not take place and is incredibly dangerous. In case where the passed object is on the stack and / or is already managed by some other owner, this is a recipe for a disaster (like a segfault).
What's more, it is more or less guaranteed to end up in a disaster, since both add_sell_obj() and remove_sell_obj() create std::unique_ptrs to potentially the same object. This is exactly the case from the original question's main(). Two std::unique_ptrs pointing to the same object result in double delete.
While it's not necessarily the best approach for this problem if one uses C++ (as compared to Java), there are couple of interesting tools that can be used for this task. The code below assumes C++20.
The class hierarchy
First of all, we need a base class that will be used when referring to all the objects stored in the shop:
struct sell_object { };
And then we need to introduce classes that will represent conrete objects:
class book : public sell_object {
std::string title;
public:
book(std::string title) : title(std::move(title)) { }
};
class table : public sell_object {
int number_of_legs = 0;
public:
table(int number_of_legs) : number_of_legs(number_of_legs) { }
};
For simplicity (but to still have some distinctions) I chose for them to have just one, distinct field (title and number_of_legs).
The storage
The shop class that will represent storage for any sell_object needs to somehow store, well, any sell_object. For that we either need to use pointers or references to the base class. You can't have a container of references, so it's best to use pointers. Smart pointers.
Originally the question suggested the usage of std::unordered_map. Let us stick with it:
class shop {
std::unordered_map<
std::unique_ptr<sell_object>, int,
> storage;
public:
auto add(...) -> void {
...
}
auto remove(...) -> void {
...
}
};
It is worth mentioning that we chose std::unique_ptr as key for our map. That means that the storage is going to copy the passed objects and use the copies it owns to compare with elements we query (add or remove). No more than one equal object will be copied, though.
The fixed version of storage
There is a problem, however. std::unordered_map uses hashing and we need to provide a hash strategy for std::unique_ptr<sell_object>. Well, there already is one and it uses the hash strategy for T*. The problem is that we want to have custom hashing. Those particular std::unique_ptr<sell_object>s should be hashed according to the associated sell_objects.
Because of this, I opt to choose a different approach than the one proposed in the question. Instead of providing a global specialization in the std namespace, I will choose a custom hashing object and a custom comparator:
class shop {
struct sell_object_hash {
auto operator()(std::unique_ptr<sell_object> const& object) const -> std::size_t {
return object->hash();
}
};
struct sell_object_equal {
auto operator()(
std::unique_ptr<sell_object> const& lhs,
std::unique_ptr<sell_object> const& rhs
) const -> bool {
return (*lhs <=> *rhs) == 0;
}
};
std::unordered_map<
std::unique_ptr<sell_object>, int,
sell_object_hash, sell_object_equal
> storage;
public:
auto add(...) -> void {
...
}
auto remove(...) -> void {
...
}
};
Notice a few things. First of all, the type of storage has changed. No longer it is an std::unordered_map<std::unique_ptr<T>, int>, but an std::unordered_map<std::unique_ptr<T>, int, sell_object_hash, sell_object_equal>. This is to indicate that we are using custom hasher (sell_object_hash) and custom comparator (sell_object_equal).
The lines we need to pay extra attention are:
return object->hash();
return (*lhs <=> *rhs) == 0;
Onto them:
return object->hash();
This is a delegation of hashing. Instead of being an observer and trying to have a type that for each and every possible type derived from sell_object implements a different hashing, we require that those objects supply the sufficient hashing themselves. In the original question, the std::hash specialization was the said "observer". It certainly did not scale as a solution.
In order to achieve the aforementioned, we modify the base class to impose the listed requirement:
struct sell_object {
virtual auto hash() const -> std::size_t = 0;
};
Thus we also need to change our book and table classes:
class book : public sell_object {
std::string title;
public:
book(std::string title) : title(std::move(title)) { }
auto hash() const -> std::size_t override {
return std::hash<std::string>()(title);
}
};
class table : public sell_object {
int number_of_legs = 0;
public:
table(int number_of_legs) : number_of_legs(number_of_legs) { }
auto hash() const -> std::size_t override {
return std::hash<int>()(number_of_legs);
}
};
return (*lhs <=> *rhs) == 0;
This is a C++20 feature called the three-way comparison operator, sometimes called the spaceship operator. I opted into using it, since starting with C++20, most types that desire to be comparable will be using this operator. That means we also need our concrete classes to implement it. What's more, we need to be able to call it with base references (sell_object&). Yet another virtual function (operator, actually) needs to be added to the base class:
struct sell_object {
virtual auto hash() const -> std::size_t = 0;
virtual auto operator<=>(sell_object const&) const -> std::partial_ordering = 0;
};
Every subclass of sell_object is going to be required to be comparable with other sell_objects. The main reason is that we need to compare sell_objects in our storage map. For completeness, I used std::partial_ordering, since we require every sell_object to be comparable with every other sell_object. While comparing two books or two tables yields strong ordering (total ordering where two equivalent objects are indistinguishable), we also - by design - need to support comparing a book to a table. This is somewhat meaningless (always returns false). Fortunately, C++20 helps us here with std::partial_ordering::unordered. Those elements are not equal and neither of them is greater or less than the other. Perfect for such scenarios.
Our concrete classes need to change accordingly:
class book : public sell_object {
std::string title;
public:
book(std::string title) : title(std::move(title)) { }
auto hash() const -> std::size_t override {
return std::hash<std::string>()(title);
}
auto operator<=>(book const& other) const {
return title <=> other.title;
};
auto operator<=>(sell_object const& other) const -> std::partial_ordering override {
if (auto book_ptr = dynamic_cast<book const*>(&other)) {
return *this <=> *book_ptr;
} else {
return std::partial_ordering::unordered;
}
}
};
class table : public sell_object {
int number_of_legs = 0;
public:
table(int number_of_legs) : number_of_legs(number_of_legs) { }
auto hash() const -> std::size_t override {
return std::hash<int>()(number_of_legs);
}
auto operator<=>(table const& other) const {
return number_of_legs <=> other.number_of_legs;
};
auto operator<=>(sell_object const& other) const -> std::partial_ordering override {
if (auto table_ptr = dynamic_cast<table const*>(&other)) {
return *this <=> *table_ptr;
} else {
return std::partial_ordering::unordered;
}
}
};
The overriden operator<=>s are required due to the base class' requirements. They are quite simple - if the other object (the one we are comparing this object to) is of the same type, we delegate to the <=> version that uses the concrete type. If not, we have a type mismatch and we report the unordered ordering.
For those of you who are curious why the <=> implementation that compares two, identical types is not = defaulted: it would use the base-class comparison first, which would delegate to the sell_object version. That would dynamic_cast again and delegate to the defaulted implementation. Which would compare the base class and... result in an infinite recursion.
add() and remove() implementation
Everything seems great, so we can move on to adding and removing items to and from our shop. However, we immediately arrive at a hard design decision. What arguments should add() and remove() accept?
std::unique_ptr<sell_object>? That would make their implementation trivial, but it would require the user to construct a potentially useless, dynamically allocated object just to call a function.
sell_object const&? That seems correct, but there are two problems with it: 1) we would still need to construct an std::unique_ptr with a copy of passed argument to find the appropriate element to remove; 2) we wouldn't be able to correctly implement add(), since we need the concrete type to construct an actual std::unique_ptr to put into our map.
Let us go with the second option and fix the first problem. We certainly do not want to construct a useless and expensive object just to look for it in the storage map. Ideally we would like to find a key (std::unique_ptr<sell_object>) that matches the passed object. Fortunately, transparent hashers and comparators come to the rescue.
By supplying additional overloads for hasher and comparator (and providing a public is_transparent alias), we allow for looking for a key that is equivalent, without needing the types to match:
struct sell_object_hash {
auto operator()(std::unique_ptr<sell_object> const& object) const -> std::size_t {
return object->hash();
}
auto operator()(sell_object const& object) const -> std::size_t {
return object.hash();
}
using is_transparent = void;
};
struct sell_object_equal {
auto operator()(
std::unique_ptr<sell_object> const& lhs,
std::unique_ptr<sell_object> const& rhs
) const -> bool {
return (*lhs <=> *rhs) == 0;
}
auto operator()(
sell_object const& lhs,
std::unique_ptr<sell_object> const& rhs
) const -> bool {
return (lhs <=> *rhs) == 0;
}
auto operator()(
std::unique_ptr<sell_object> const& lhs,
sell_object const& rhs
) const -> bool {
return (*lhs <=> rhs) == 0;
}
using is_transparent = void;
};
Thanks to that, we can now implement shop::remove() like so:
auto remove(sell_object const& to_remove) -> void {
if (auto it = storage.find(to_remove); it != storage.end()) {
it->second--;
if (it->second == 0) {
storage.erase(it);
}
}
}
Since our comparator and hasher are transparent, we can find() an element that is equivalent to the argument. If we find it, we decrement the corresponding count. If it reaches 0, we remove the entry completely.
Great, onto the second problem. Let us list the requirements for the shop::add():
we need the concrete type of the object (merely a reference to the base class is not enough, since we need to create matching std::unique_ptr).
we need that type to be derived from sell_object.
We can achieve both with a constrained* template:
template <std::derived_from<sell_object> T>
auto add(T const& to_add) -> void {
if (auto it = storage.find(to_add); it != storage.end()) {
it->second++;
} else {
storage[std::make_unique<T>(to_add)] = 1;
}
}
This is, again, quite simple
*References: {1} {2}
Correct destruction semantics
There is only one more thing that separates us from the correct implementation. It's the fact that if we have a pointer (either smart or not) to a base class that is used to deallocate it, the destructor needs to be virtual.
This leads us to the final version of the sell_object class:
struct sell_object {
virtual auto hash() const -> std::size_t = 0;
virtual auto operator<=>(sell_object const&) const -> std::partial_ordering = 0;
virtual ~sell_object() = default;
};
See full implementation with example and additional printing utilities.
This question already has answers here:
How to call a parent class function from derived class function?
(7 answers)
Closed 8 years ago.
I'm going through a transition from Java to C++ and am trying to write a simple program.
There's a superclass Animal with the following inteface:
class Animal
{
public:
Animal(int a, std::string n);
bool operator==(Animal a);
private:
int age;
std::string name;
};
And it's subclass Dog:
class Dog : public Animal
{
public:
Dog(int a, std::string n, double w);
bool operator==(Dog d);
private:
double weight;
};
My question is in regards to the Dog's operator== method, which compares 2 dogs.
Animal's operator== is below.
bool Animal::operator==(Animal a) //overriding of operator ==
{
return (age==a.age) && (name==a.name);
}
Now I want to write the Dog's version using Animal's method.
Like I'd do in Java:
boolean equals(Dog d){
return (super.equals(d)) && (this.name==d.name);
}
What I need is the c++ equivalent of (super.equals(d)) . If it was a method with a normal name it would be easy(Animal::equals(d)), but I don't know how to do it for operator==, which has a diferent syntax.
It's actually surprisingly easy:
return Animal::operator==(d) && name==d.name;
The reason for using the superclass' name rather than super is that in C++ you can have multiple superclasses, so you have to be clear about which one you want.
Alternatively, you can call it via using it's overload:
return ((Animal&)*this)==d && name==d.name;
Since the paramters to operator== in this case would be an Animal& and a Dog&, then it can't match Dog::operator==(Dog d), and so uses Animal::operator==(Animal a) instead.
Note: Your signatures are highly unusual. Instead, use one of these:
bool operator==(const Animal& a) const;
friend bool operator==(const Animal& left, const Animal& right);
These don't make copies of animals each time you compare, and can compare const animals.
You can call the operator using verbose notation:
operator==(const Dog& dog) { return Animal::operator==(dog) && weight==dog.weight; }
The direct equivalent of your Java equals would be:
bool Dog::operator==(Dog d)
{
return Animal::operator==(d) && weight == d.weight;
}
But I'm not sure if this is what you really want. For starters,
you're taking the argument by copy, which means that your
comparing a copy. In particular, when you call
Animal::operator==, you will pass a copy of the Animal part
of Dog, not the complete object. Class types are usually
passed by reference; reference to const if you don't want to
modify them. So the signature in the base would be something
like:
bool Animal::operator==( Animal const& other ) const
{
// ...
}
And similarly in Dog. Also, the comparison operator in Dog
would probably take an Animal const&, not a Dog const&. (In
Java, equals takes a java.lang.Object, always.) Which means
that you'd have to verify that it was a Dog:
bool Dog::operator==( Animal const& other ) const
{
return dynamic_cast<Dog const*>( &other ) != nullptr
&& Animal::operator==( other )
&& weight == other.weight;
}
EDIT:
As was pointed out in a comment, while this addresses the
immediate syntax issue raised by the original poster, it isn't
really the way we'd do this normally. The usual solution would
be something like:
class Animal
{
// If Animal is actually an abstract class (usually the case
// in real code), this would be a pure virtual.
// Derived classes overriding this function are guaranteed
// that other is actually of the same type as they are,
// so they can just static_cast it to their type.
virtual bool doIsEqual( Animal const& other ) const
{
return true;
}
public:
bool isEqual( Animal const& other )
{
return typeid( *this ) == typeid( other )
&& // ... his local conditions...
&& doIsEqual( other );
}
};
bool operator==( Animal const& lhs, Animal const& rhs )
{
return lhs.isEqual( rhs );
}
bool operator!=( Animal const& lhs, Animal const& rhs )
{
return !lhs.isEqual( rhs );
}
The implementation of operator== and operator!= can in fact
be done by inheriting from an appropriate class template, which
avoids some of the boilerplate if you have a lot of classes
which must support == and the others.
This question already has answers here:
How can I use std::maps with user-defined types as key?
(8 answers)
Closed 9 years ago.
I am trying to create a set of objects. Merely defining the set is giving errors. I do not have a compiler right now with boost. So I used an online IDE. Here is the link to the code http://codepad.org/UsBAMmuh. Somehow, it does not seem to work. Eventually, I would like to pass a reference to this set in the constructor of another class.
#include <iostream>
#include <boost/optional.hpp>
#include <boost/ref.hpp>
#include <boost/serialization/vector.hpp>
using namespace std;
class Fruit {
};
class Env
{
public:
Env(std::set<Fruit>& apples);
std::set<Fruit>& GetApples() const;
void AddApple(Fruit const& fruit);
private:
std::set<Fruit>& _apples;
};
Env::Env(std::set<Fruit>& apples):
_apples(apples)
{
}
std::set<Fruit>& Env::GetApples() const
{
return _apples;
}
void Env::AddApple(Fruit const& fruit)
{
this->_apples.insert(fruit);
}
class EnvHolder{
public:
void SetEnv (Env const& env);
Env& GetEnv()const;
private:
boost::scoped_ptr<Env> _env;
};
void EnvHolder::SetEnv(Env const& env)
{
this->_env.reset(new Env(env));
}
Env& EnvHolder::GetEnv() const
{
return *this->_env;
}
int main() {
std::set<Fruit> fruits;
//Fruit *fr = new Fruit();
//fruits.insert(*fr);
//Env env(fruits);
cout << "Hello" << endl;
return 0;
}
I get the following error:
/usr/local/lib/gcc/i686-pc-linux-gnu/4.1.2/../../../../include/c++/4.1.2/bits/stl_function.h: In member function 'bool std::less<_Tp>::operator()(const _Tp&, const _Tp&) const [with _Tp = Fruit]':
/usr/local/lib/gcc/i686-pc-linux-gnu/4.1.2/../../../../include/c++/4.1.2/bits/boost_concept_check.h:358: instantiated from 'void __gnu_cxx::_BinaryFunctionConcept<_Func, _Return, _First, Second>::_constraints() [with _Func = std::less, _Return = bool, _First = Fruit, _Second = Fruit]'
/usr/local/lib/gcc/i686-pc-linux-gnu/4.1.2/../../../../include/c++/4.1.2/bits/stl_set.h:112: instantiated from '__gnu_norm::set, std::allocator >'
/usr/local/lib/gcc/i686-pc-linux-gnu/4.1.2/../../../../include/c++/4.1.2/debug/set.h:45: instantiated from '__gnu_debug_def::set, std::allocator >'
t.cpp:35: instantiated from here
Line 226: error: no match for 'operator<' in '__x < __y'
compilation terminated due to -Wfatal-errors.
When you want to put objects of a class into an associative container, e.g., a std::set<T>, you'll need to provide some way to identify the objects in that container. The ordered associative containers (std::map<K, V>, std::set<T> and their "multi"-versions) use a strict weak ordering to to identify object. This means you either define a suitable less than operator or you provide a comparison type when defining the std::set<T>. For starters it is probably easiest to define a less than operator, e.g.:
class Fruit {
std::string name_;
public:
Fruit(std::string const& name): name_(name) {}
std::string name() const { return this->name_; }
};
bool operator< (Fruit const& f0, Fruit const& f1) {
return f0.name() < f1.name();
}
bool operator> (Fruit const& f0, Fruit const& f1) { return (f1 < f0); }
bool operator<= (Fruit const& f0, Fruit const& f1) { return !(f1 < f0); }
bool operator>= (Fruit const& f0, Fruit const& f1) { return !(f0 < f1); }
Although adding these operators will make your code compile, it probably won't make it work too well: since the Fruit class has no members, there is no way to distinguish them. Hence, all Fruit objects are considered to be the in the same equivalence class. For an actual Fruit class you would have some members and you would order your objects according to them.
Although strictly speaking only operator<() is needed, it is reasonable to provide the other relation operators, too. They always look the same, though, i.e., they simply delegate to operator<(). Aside from choosing to implement full set of relation operators, the code also chooses to implement the comparison operators as non-member functions: the operators could also be implemented as member function. However, if there are implicit conversion involved, the member operators behave asymmetric: the allow conversions on the right hand side operand but not on the left hand side operand. The non-member implementation make the conversion behavior symmetrical.
Using a comparison type would look something like this:
struct FruitCmp {
bool operator()(Fruit const& f0, Fruit const& f1) const {
return f0.name() < f1.name();
}
};
std::set<Fruit, FruitCmp> setOfFruit;
Of course, in all these case where false is return the proper logic implementing a strict weak ordering need to go. Typically, the easiest approach is to define the comparison in terms of a lexicographical comparison of the key members.