C++: STL: set: stored value constness - c++

Having the following code:
#include <iostream>
#include <set>
#include <string>
#include <functional>
using namespace std;
class Employee {
// ...
int _id;
string _name;
string _title;
public:
Employee(int id): _id(id) {}
string const &name() const { return _name; }
void setName(string const &newName) { _name = newName; }
string const &title() const { return _title; }
void setTitle(string const &newTitle) { _title = newTitle; }
int id() const { return _id; }
};
struct compEmployeesByID: public binary_function<Employee, Employee, bool> {
bool operator()(Employee const &lhs, Employee const &rhs) {
return lhs.id() < rhs.id();
}
};
int wmain() {
Employee emplArr[] = {0, 1, 2, 3, 4};
set<Employee, compEmployeesByID> employees(emplArr, emplArr + sizeof emplArr/sizeof emplArr[0]);
// ...
set<Employee, compEmployeesByID>::iterator iter = employees.find(2);
if (iter != employees.end())
iter->setTitle("Supervisor");
return 0;
}
I cannot compile this code having (MSVCPP 11.0):
1> main.cpp
1>d:\docs\programming\test01\test01\main.cpp(40): error C2662: 'Employee::setTitle' : cannot convert 'this' pointer from 'const Employee' to 'Employee &'
1> Conversion loses qualifiers
This helps to compile:
if (iter != employees.end())
const_cast<Employee &>(*iter).setTitle("Supervisor");
The question: I know that map and multimap store their values as pair(const K, V) where K is a key and V is a value. We cannot change the K object. But set<T> and multiset<T> store their object as T, not as const T. So WHY I NEED THIS CONST CAST??

In C++11 set (and multiset) specify that iterator as well as const_iterator is a constant iterator, i.e. you cannot use it to modify the key. This is because any modification of they key risks breaking the set's invariant. (See 23.2.4/6.)
Your const_cast opens the door to undefined behaviour.

The values in a set are not supposed to be modified. For example, if you modified your Employee's ID, then it would be in the wrong position in the set and the set would be broken.
Your Employee has three fields, and your set is using the _id field in your operator<.
class Employee {
// ...
int _id;
string _name;
string _title;
};
Therefore, you should probably use a map<int,Employee> instead of your set, then you would be able to modify the name and title. I would also make the _id field of Employee a const int _id.
(By the way, identifiers beginning with _ are technically reserved and should be avoided. It's never cause me any trouble but now I prefer to put the underscore on the end of the variable name.)

In C++, you cannot modify keys of associated STL containers because you will break their ordering. When you wish to change a key, you're supposed to (1) find the existing key, (2) delete it, and (3) insert the new key.
Unfortunately, while this isn't overly appealing, it's how associative containers work in the STL.

You can get away with const with just an indirection.
But be careful to not change the ordering of the elements in a given sorted container.

Related

I can't redefine operator[] for const structure [duplicate]

#include <iostream>
#include <set>
using namespace std;
class StudentT {
public:
int id;
string name;
public:
StudentT(int _id, string _name) : id(_id), name(_name) {
}
int getId() {
return id;
}
string getName() {
return name;
}
};
inline bool operator< (StudentT s1, StudentT s2) {
return s1.getId() < s2.getId();
}
int main() {
set<StudentT> st;
StudentT s1(0, "Tom");
StudentT s2(1, "Tim");
st.insert(s1);
st.insert(s2);
set<StudentT> :: iterator itr;
for (itr = st.begin(); itr != st.end(); itr++) {
cout << itr->getId() << " " << itr->getName() << endl;
}
return 0;
}
In line:
cout << itr->getId() << " " << itr->getName() << endl;
It give an error that:
../main.cpp:35: error: passing 'const StudentT' as 'this' argument of 'int StudentT::getId()' discards qualifiers
../main.cpp:35: error: passing 'const StudentT' as 'this' argument of 'std::string StudentT::getName()' discards qualifiers
What's wrong with this code? Thank you!
The objects in the std::set are stored as const StudentT. So when you try to call getId() with the const object the compiler detects a problem, mainly you're calling a non-const member function on const object which is not allowed because non-const member functions make NO PROMISE not to modify the object; so the compiler is going to make a safe assumption that getId() might attempt to modify the object but at the same time, it also notices that the object is const; so any attempt to modify the const object should be an error. Hence compiler generates an error message.
The solution is simple: make the functions const as:
int getId() const {
return id;
}
string getName() const {
return name;
}
This is necessary because now you can call getId() and getName() on const objects as:
void f(const StudentT & s)
{
cout << s.getId(); //now okay, but error with your versions
cout << s.getName(); //now okay, but error with your versions
}
As a sidenote, you should implement operator< as :
inline bool operator< (const StudentT & s1, const StudentT & s2)
{
return s1.getId() < s2.getId();
}
Note parameters are now const reference.
Member functions that do not modify the class instance should be declared as const:
int getId() const {
return id;
}
string getName() const {
return name;
}
Anytime you see "discards qualifiers", it's talking about const or volatile.
Actually the C++ standard (i.e. C++ 0x draft) says (tnx to #Xeo & #Ben Voigt for pointing that out to me):
23.2.4 Associative containers
5 For set and multiset the value type
is the same as the key type. For map
and multimap it is equal to pair. Keys in an associative
container are immutable.
6 iterator of
an associative container is of the
bidirectional iterator category. For
associative containers where the value
type is the same as the key type, both
iterator and const_iterator are
constant iterators. It is unspecified
whether or not iterator and
const_iterator are the same type.
So VC++ 2008 Dinkumware implementation is faulty.
Old answer:
You got that error because in certain implementations of the std lib the set::iterator is the same as set::const_iterator.
For example libstdc++ (shipped with g++) has it (see here for the entire source code):
typedef typename _Rep_type::const_iterator iterator;
typedef typename _Rep_type::const_iterator const_iterator;
And in SGI's docs it states:
iterator Container Iterator used to iterate through a set.
const_iterator Container Const iterator used to iterate through a set. (Iterator and const_iterator are the same type.)
On the other hand VC++ 2008 Express compiles your code without complaining that you're calling non const methods on set::iterators.
Let's me give a more detail example. As to the below struct:
struct Count{
uint32_t c;
Count(uint32_t i=0):c(i){}
uint32_t getCount(){
return c;
}
uint32_t add(const Count& count){
uint32_t total = c + count.getCount();
return total;
}
};
As you see the above, the IDE(CLion), will give tips Non-const function 'getCount' is called on the const object. In the method add count is declared as const object, but the method getCount is not const method, so count.getCount() may change the members in count.
Compile error as below(core message in my compiler):
error: passing 'const xy_stl::Count' as 'this' argument discards qualifiers [-fpermissive]
To solve the above problem, you can:
change the method uint32_t getCount(){...} to uint32_t getCount() const {...}. So count.getCount() won't change the members in count.
or
change uint32_t add(const Count& count){...} to uint32_t add(Count& count){...}. So count don't care about changing members in it.
As to your problem, objects in the std::set are stored as const StudentT, but the method getId and getName are not const, so you give the above error.
You can also see this question Meaning of 'const' last in a function declaration of a class? for more detail.

C++ sorting a vector of shared_ptrs

I'm trying to sort a vector of shared_ptrs to Food objects.
Food class is defined as:
class Food {
private:
// Human-readable description of the food, e.g. "all-purpose wheat
// flour". Must be non-empty.
std::string _description;
// Human-readable description of the amount of the food in one
// sample, e.g. "1 cup". Must be non-empty.
std::string _amount;
// Number of grams in one sample; must be non-negative.
int _amount_g;
// Energy, in units of kilocalories (commonly called "calories"), in
// one sample; must be non-negative.
int _kcal;
// Number of grams of protein in one sample; most be non-negative.
int _protein_g;
public:
Food(const std::string& description,
const std::string& amount,
int amount_g,
int kcal,
int protein_g)
: _description(description),
_amount(amount),
_amount_g(amount_g),
_kcal(kcal),
_protein_g(protein_g) {
assert(!description.empty());
assert(!amount.empty());
assert(amount_g >= 0);
assert(kcal >= 0);
assert(protein_g >= 0);
}
const std::string& description() const { return _description; }
const std::string& amount() const { return _amount; }
int amount_g() const { return _amount_g; }
int kcal() const { return _kcal; }
int protein_g() const { return _protein_g; }
};
using
// Alias for a vector of shared pointers to Food objects.
using FoodVector = std::vector<std::shared_ptr<Food>>;
My sorting algorithm is:
std::unique_ptr<FoodVector> greedy_max_protein(const FoodVector& foods,
int total_kcal)
{
std::unique_ptr<FoodVector> result(new FoodVector);
int result_cal = 0;
sort(foods.begin(), foods.end(), sortByProtein); //sorting error
...
The error is occuring with the sort function here ^^ and my sortByProtein function is:
bool sortByProtein(const std::shared_ptr<Food>&lhs, const std::shared_ptr<Food>&rhs)
{
return lhs->protein_g() > rhs->protein_g();
}
I keep getting the binary'='no operator found which takes a left hand operand type 'const std::shared_ptr' or there is no acceptable conversion. I've tried creating my own sort function but i get the same error. Do i need to overload operator= in my class ? If so how do i go about doing that? Any help would be greatly appreciated!
EDIT
fixed the issue by creating a new pointer:
FoodVector *sorted = new FoodVector(foods);
Thanks!
I chopped down and isolated the problem.
Before removing the "const" I got the same error you were getting.
But this compiles.
#include <iostream>
//:For: std::vector
#include <vector>
//:For: std::shared_ptr
#include <memory>
//:For: std::sort
#include<algorithm>
class Food{ /* SomeLogicHere */ };
typedef std::vector<std::shared_ptr<Food>> FoodVector;
bool operator==(
std::shared_ptr<Food> lhs,
std::shared_ptr<Food> rhs
){
return true; //TODO: Actual Comparison Logic
}
bool sortByProtein(
std::shared_ptr<Food> lhs,
std::shared_ptr<Food> rhs
) {
//:Dont Care about implementation.
//:Just want minimal example that
//:gets the error.
return false;
}
std::unique_ptr<FoodVector> greedy_max_protein(
FoodVector foods,
int total_kcal
){
sort(foods.begin(),foods.end(),sortByProtein);
}
int main()
{
std::cout<<"Hello World";
return 0;
}
The problem is in the function that is trying to sort
std::unique_ptr<FoodVector> greedy_max_protein(const FoodVector& foods,
int total_kcal)
{
std::unique_ptr<FoodVector> result(new FoodVector);
int result_cal = 0;
sort(foods.begin(), foods.end(), sortByProtein); //sorting error
std::sort() relies on the first two arguments being non-const iterators - i.e. that can be used to change the values they refer to. It is, after all, rather tough to sort a container if the elements cannot be reassigned.
foods is a const reference to a FoodVector (aka std::vector<std::shared_ptr<Food> >) so the begin() and end() functions are const, and return a type std::vector<std::shared_ptr<Food> >::const_iterator. That does not meet requirements of std::sort().
To fix the problem, either
remove the const qualifier from the first argument of the function greedy_max_protein(). Be aware this means that the function may change the elements of the passed FoodVector, and the caller will not be able to pass a const FoodVector;
Create a copy locally in the function and sort that. This is appropriate if the working of the function needs a sorted vector, but the caller requires that the passed vector remains unchanged.

How to achieve a data structure like "QHash<int, Foo> bar;" where "bar[10]" returns all "Foo" belonging to keys of 10 or less?

It does not have to be a QHash; just an existing data structure (ideally in Qt) which cleanly accomplishes that task without being considered an esoteric solution, because I need this code to be quite short (to fit on a small playing card) and easily understandable. Vectors, Multi-hashes, Lists, Maps, or anything is welcome, as long as it would be considered good practice.
Basically, I have a class which has an integer value associated with it. For example:
class Flowers {
public:
const int m_Cost;
Flowers(int cost) { m_Cost = cost; }
}
Flowers roses{5};
Flowers violets{7};
Flowers tulips{9};
Flowers posies{3};
/* Place them in some sort of datastructure. */
flowerDataStructure[4]; // Returns Posies
flowerDataStructure[7]; // Returns Violets, Roses, Posies
flowerDataStructure[roses.m_Cost]; // Returns Roses, Posies
Would they perhaps support a range such as,
flowerDataStructure[5 ... 11]; // Returns Roses, Violets, Tulips
PS: int m_Cost; does not have to be const. I just assumed it would be easier if it was.
Thanks.
How to achieve a data structure like “QHash bar;” where
“bar[10]” returns all “Foo” belonging to keys of 10 or less?
The right datastructure for the collection of non-unique items sorted by integral value would be either std::multimap or std::multiset depending on how we store the key. According to the authors example above the key is stored with the data type so I have chosen std::multiset:
#include <set>
#include <string>
#include <QDebug>
struct Flower
{
public:
const int m_cost;
const std::string m_name;
explicit Flower(int cost) : m_cost(cost) {}
Flower(const char* name, int cost) : m_cost(cost), m_name(name) {}
};
int main()
{
auto lessFunc = [](const Flower& l, const Flower& r) -> bool
{return l.m_cost < r.m_cost;};
std::multiset<Flower, decltype(lessFunc)> multiSet(lessFunc);
multiSet.emplace("roses", 5);
multiSet.emplace("violets", 7);
multiSet.emplace("tulips", 9);
multiSet.emplace("posies", 3);
// This is a request for items equal or below 7
const auto& itEnd = multiSet.upper_bound(Flower{7});
for(auto it = multiSet.begin(); it != itEnd; it++)
{
const Flower& flower{*it};
qDebug() << flower.m_name.c_str() << flower.m_cost;
}
return 0;
}
If operator [upper_bound] desired then it can be done but we have to overload std::multiset (more work).

C++: two vectors of pointers to the same structure, each differently sorted

I have a class with a structure of persons. Each person has its own name, primary ID and secondary ID. For effective searching I have implemented binary search. The problem is that I would like to effectively (better than O(n)) search persons not only by their primary ID, but also by their secondary ID. But the persons can be sorted only by one parameter. So I have thought if its possible to make two vectors of pointers to the persons structure, one sorted by their primary ID and the other one by their secondary ID.
For example, two persons: Mark Smith with primary ID 9845613 and secondary ID 1312469 and John Doyle, primary 3213444, secondary 2654722. So the first element of m_byPrim vector and second element of m_bySec vector should be pointing to John Doyle.
Is that even possible? Here is a part of the relevant code I have managed to write so far:
#include <cstdlib>
#include <cstdio>
#include <vector>
#include <string>
using namespace std;
class CPersons
{
public:
bool AddPerson (const string &name,
unsigned int primID,
unsigned int secID);
private:
struct Person {
Person (const string & init_name,
unsigned int init_primID,
unsigned int init_secID)
: m_name (init_name), m_primID (init_primID), m_secID (init_secID){}
string m_name;
unsigned int m_primID;
unsigned int m_secID;
};
vector<Person*>m_byPrim;
vector<Person*>m_bySec;
};
bool CPersons::AddPerson (const string &name,
unsigned int primID,
unsigned int secID){
int positionPrim;
int positionSec;
return false;
}
int main ( void )
{
CPersons a;
a.AddPerson ( "Mark Smith", 9845613, 1324697 );
a.AddPerson ( "John Doyle", 3213444, 2654722 );
return 0;
}
The position integer is the result of my binary search function (position to which insert persons), I have managed to implement that successfully, so just assume that this position will be already initialized.
But my problem is how to implement the adding to the vector of pointers? I am still pretty new to the pointers (and C++ in general), so I don't know if my thought is valid and possible. Thank you for any tips & help.
edit: I forgot to mention that from STL containers I can use only vectors.
Instead of using sorted vectors, try using a std::map:
class CPersons
{
public:
bool AddPerson (const string &name, unsigned int primID, unsigned int secID);
private:
struct Person {
Person (const string & init_name, unsigned int init_primID, unsigned int init_secID)
: m_name (init_name), m_primID (init_primID), m_secID (init_secID) {}
string m_name;
unsigned int m_primID;
unsigned int m_secID;
};
map<unsigned int, Person*>m_byPrim;
map<unsigned int, Person*>m_bySec;
};
bool CPersons::AddPerson (const string &name, unsigned int primID, unsigned int secID){
p = new Person(name, primID, secID);
m_byPrim[primID] = p;
m_bySec[secID] = p;
return false;
}
The drawbacks of using a vector:
If you insert a person anywhere other than the end of the list, everything already in the vector has to "bubble down", being copied to its new position in the list.
As the vector grows, it will periodically need to be reallocated and the entire set of existing elements copied to the new underlying array.
The benefits of using a map:
The impact of adding new elements to the list is minimal
The interface for adding and looking up elements is more convenient and readable than finding things up in a sorted vector.
Since you can't use anything but vector, here's how your AddPerson function should look:
bool CPersons::AddPerson(
const string &name, unsigned int primID, unsigned int secID) {
// I'm assuming that the two functions below binary-search to get
// the positions where inserting the elements into the arrays would
// maintain their sorting
int positionPrim = getPositionPrim();
int positionSec = getPositionSec();
Person *person = new Person(name, primID, secID);
m_byPrim.insert(m_byPrim.begin() + positionPrim, person);
m_bySec.insert(m_bySex.begin() + positionSec, person);
return false;
}
You'll need to define a destructor as well because you're using dynamic memory:
CPersons::~CPersons()
{
for (const auto &i: m_byPrim) {
delete i;
}
}

Maintaining a unique set of elements on different criteria C++ STL

I have to develop a component which
will have a more than
100,000 instances of a class. And i
want to generate a report based on the
different different criteria (members)
of the particular class. for example,
A employee class with data fields id,
names, addr, phoneno. Report
generation wiil be based on
names_ascending
names_descending
addr_ascending
phoneno_asceding
unique_names
unique_addr
unique_phoneno
runtime iteration of instances for each call is very slow since it is a linear operation on large number of instances and requires sorting mechanism.
So i have stored a pointers of each instances in a container on different sorted manner. But requires more memory than required. Please suggest me a better way of doing this. I have posted sample code snippet that i followed to achieve above.
class Employee
{
int m_id;
string m_name;
string m_addr;
string m_phone;
public:
Employee(int id, string name, string addr, string phone) :
m_id(id), m_name(name), m_addr(addr), m_phone(phone) { }
int id() const { return m_id; }
string name() const { return m_name; }
string addr() const { return m_addr; }
string phoneno() const { return m_phone; }
};
//custom predicate for std containers
struct IDComparator
{
bool operator() (const Employee* e1, const Employee* e2 )
{
return e1->id() < e2->id();
}
};
struct NameComparator
{
bool operator() (const Employee* e1, const Employee* e2 )
{
return e1->name() < e2->name();
}
}
struct AddressComparator
{
bool operator() (const Employee* e1, const Employee* e2 )
{
return e1->addr() < e2->addr();
}
};
struct PhoneComparator
{
bool operator() (const Employee* e1, const Employee* e2 )
{
return e1->phoneno() < e2->phoneno();
}
};
//Class which holds huge number of employee instances
class Dept
{
private:
typedef set<Employee*, IDComparator> EMPID; //unnique id
typedef EMPID::iterator EMPID_ITER;
typedef multiset<const Employee*, NameComparator> EMPNAME; // for sorted names
typedef EMPNAME::iterator NAME_ITER;
typedef multiset<const Employee*, AddressComparator> EMPADDR; // for sorted addr
typedef EMPADDR::iterator ADDR_ITER;
typedef multiset<const Employee*, PhoneComparator> EMPPHONE; // for sorted phoneno
typedef EMPPHONE::iterator PHONE_ITER;
private:
EMPID m_empids;
EMPNAME m_names ;
EMPADDR m_addr;
EMPPHONE m_phoneno;
public:
Dept() { }
~Dept() { //delete the instances of employees }
void add(Employee* e)
{
EMP_ITER iter = m_empids.insert(e).first;
const Employee* empptr = &*iter;
m_names.insert(empptr); // adds employee pointer to name multimap
m_addr.insert(empptr); // adds employee pointer to addr multimap
m_phoneno.insert(empptr); // adds employee pointer to phone multimap
}
void print_emp_dtls() const; //prints all the emp dtls iterating though EMPID
void print_unique_names() const; //iterate EMPNAME & use upperbound & lowerbound, prints unique names
void print_asc_name() const; //iterate EMPNAME & prints all names in ascending order
void print_desc_name() const; //back iterate EMPNAME & prints all names in descending order
void print_unique_adrr() const; //iterate EMPADDR & use upperbound & lowerbound, prints unique address
void print_asc_addr() const; //iterate EMPADDR & prints all addr in ascending order
void print_desc_addr() const; //back iterate EMPADDR & prints all address in descending order
void print_unique_phoneno() const; //iterate EMPPHONE & use upperbound & lowerbound,prints unique phoneno
void print_asc_phoneno() const; //iterate EMPPHONE & prints all phoneno in ascending order
void print_desc_phoneno() const; //back iterate EMPPHONE & prints all phoneno in };
Seems like a perfect candidate for Boost.MultiIndex :
The Boost Multi-index Containers
Library provides a class template
named multi_index_container which
enables the construction of containers
maintaining one or more indices with
different sorting and access
semantics.
I have used Boost.Multi_index successfully in the past. You might find it strange from a first look but in reality it is quit interesting library. Keep in mind when using it, that you don't provide "how" but "what" in your customized container. Assume that you have the following type:
struct user_t
{
string id, name, email;
int age;
friend ostream& operator<<(ostream& output_stream, const user_t& user)
{
return output_stream
<< user.id << " "
<< user.name << " "
<< user.age << " "
<< user.email << "\n";
}
friend istream& operator>>(istream& input_stream, user_t& user)
{
return input_stream >> user.id >> user.name >> user.age >> user.email;
}
};
What will happen is that you create one container, that holds the objects and as many indices as you want. Before we start lets define the tags of indices. The tags are simply tags! that you use to access your indices by name instead of by magical numbers:
struct by_id { };
struct by_name { };
struct by_age { };
struct by_email { };
Then we define our "data base" with the required indices:
typedef multi_index_container<
user_t,
indexed_by
<
ordered_unique<tag<by_id>, member<user_t, string, &user_t::id> >,
ordered_non_unique<tag<by_name>, member<user_t, string, &user_t::name> >,
ordered_non_unique<tag<by_age>, member<user_t, int, &user_t::age> >,
ordered_non_unique<tag<by_email>, member<user_t, string, &user_t::email> >
>
> user_db;
First thing is the type of elements in the container. Then, you say I want to index this container by the following:
indexed_by
<
ordered_unique<tag<by_id>, member<user_t, string, &user_t::id> >,
ordered_non_unique<tag<by_name>, member<user_t, string, &user_t::name> >,
ordered_non_unique<tag<by_age>, member<user_t, int, &user_t::age> >,
ordered_non_unique<tag<by_email>, member<user_t, string, &user_t::email> >
>
You just specify the type of index you want expose. There are various types actually, and it depends on the semantics of the data you have. It is good to give a tag for each index(the first parameter), and you specify you want to index the type by what through the second template parameter. There are various ways actually to choose the "key" of the data. The key is not required to be unique actually!
From now on, you just deal with user_db just like regular std::multi_set! with a small difference that makes the difference actually ;) Lets say you want to load serilaized users' information from a file, and reserlize ordered information according to the indecies we created:
user_db load_information()
{
ifstream info_file("information.txt");
user_db db;
user_t user;
while(info_file >> user)
db.insert(user);
return db;
}
template <typename index_t>
void save_information_by(ostream& output_stream, const index_t& index)
{
ostream_iterator<user_t> serializer(output_stream);
copy(index.begin(), index.end(), serializer);
}
int main()
{
ofstream
by_id_file("by_id.txt"),
by_name_file("by_name.txt"),
by_age_file("by_age.txt"),
by_email_file("by_email.txt");
user_db db = load_information();
// You see why we created the tags,
// if we didn't we had to specify the index like the following:
// const auto& name_index = db.get<by_name>(); ==
// const auto& name_index = db.get<1>();
const auto& id_index = db.get<by_id>();
const auto& name_index = db.get<by_name>();
const auto& age_index = db.get<by_age>();
const auto& email_index = db.get<by_email>();
save_information_by(by_id_file, id_index);
save_information_by(by_name_file, name_index);
save_information_by(by_age_file, age_index);
save_information_by(by_email_file, email_index);
}
Look at boost::multi_index here. There is a container boost::multi_index_contaier which allows you to search for items using various keys.