Sid
Sid

Reputation: 91

I want to perform in place modification to a std::map while iterating over it

I need to know the best way to do the in place modification to the map without taking a local copy of the values modified and then pushing it again into the original map.

I have detailed the snippet below that explains the problem:

#include <string>
#include <map>
struct EmployeeKey
{
    std::string name;
    int amount;
    int age;
};

struct EmployeeDetail
{
    std::string dept;
    int section;
    int salary;
};

bool compareByNameAge(const std::string& name,
                      const int& age,
                      const EmployeeKey& key )
{
    return name > key.name && age > key.age;
}

typedef std::map<EmployeeKey, EmployeeDetail> EmployeeMap;

int main()
{
    EmployeeMap eMap;
    // insert entries to the map
    int age = 10;
    std::string name = "John";

    EmployeeMap transformMap;
    foreach( iter, eMap )
    {
        if ( compareByNameAge(name, age, iter->first) )
        {
            //**This is what i want to avoid.......
            // take a copy of the data modified
            // push it in a new map.
            EmployeeDetail det = iter->second;
            det.salary = 1000;
            transformMap[iter->first] = det;
        }
    }

    //** Also, i need to avoid too...  
    // do the cpy of the modified values   
    // from the transform map to the   
    // original map  
    foreach( iter1, transformMap )  
        eMap[iter1->first] = iter1->second;
}  

Upvotes: 9

Views: 11441

Answers (10)

Expurple
Expurple

Reputation: 931

C++17 update with structured bindings:

for (auto &[key, detail] : employeeMap)
    if (compareByNameAge(name, age, key))  
        detail.salary = 1000;   

Upvotes: 0

Mark B
Mark B

Reputation: 96311

You can't use std::transform because it assigns iterators, and the first element of a map iterator is always const.

Additionally your code doesn't show us the comparison for your employee key, so I'll assume you have one that implements strict weak ordering. A basic outline:

You can use for_each though, since the predicate can be stateless:

class SalaryUpdater
{
public:
    SalaryUpdater(const std::string& name, int age) : name_(name), age_(age) { }

    void operator()(EmployeeMap::value_type& item)
    {
        if(compareByNameAge(name_, age_, item.first))
        {
            item.second.salary = 1000;
        }
    }

private:
    std::string name_;
    int age_;
};

int main()
{
    EmployeeMap eMap;
    // insert entries to the map

    std::for_each(eMap.begin(), eMap.end(), SalaryUpdater("John", 10));
}

Upvotes: 1

Robᵩ
Robᵩ

Reputation: 168876

If you have C++11, try this:

for (auto& pair : eMap )
  if (pair.first.name == "Rob")
    pair.second.salary *= 1000;

Note: you can only change pair.second. pair.first is const, and must not be changed (it is, after all, the key to the map.)

If you don't have C++11, try this:

for(EmployeeMap::iterator it = eMap.begin(); it != eMap.end(); ++it)
  if(pair.first.name == "Rob")
    pair.second.hours /= 2;

Upvotes: 0

Tom Tanner
Tom Tanner

Reputation: 9354

Wouldn't just iterating over the map and doing

iter->second.salary = 1000;

solve your problem?

Upvotes: 1

Grizzly
Grizzly

Reputation: 20211

You can simply modifiy the element directly through the iterator (which points directly to the corresponding item):

foreach(iter, eMap)  
{  
   if (compareByNameAge(name, age, iter->first))  
     iter->second.salary = 1000;   
} 

for more complex modifications you could take the value by reference:

EmployeeDetail& det = iter->second;  
det.salary = 1000;  

In c++ you can typically not modify a collection while iterating, but that only means that you can't remove/add items. Modifying existing items is typically fine in C++11. What you can't modify is the key in a map and any part of the element in set, but those are const in c++11 anyways, so you can't modify those. In C++03 you need to remember not to change the keypart of an element in a set.

Upvotes: 9

liwp
liwp

Reputation: 6926

Can't you just do the following?

it->second.salary = 1000;

Upvotes: 2

StilesCrisis
StilesCrisis

Reputation: 16320

It's fine to alter the values of the map objects (the second part of the value_type) during foreach iteration. You just can't add or remove any keys--no insert or erase.

Upvotes: 1

Nim
Nim

Reputation: 33655

iter->second is a reference to the EmployeeDetail object, which you can modify directly - e.g.

   foreach( iter, eMap )  
   {  
       if ( compareByNameAge(name, age, iter->first) )  
       {
           iter->second.salary = 1000;
       }  
   }  

No need for the transformMap

Upvotes: 6

Ben
Ben

Reputation: 4676

Get a reference to the EmployeeDetail.

Upvotes: 0

Andr&#233; Caron
Andr&#233; Caron

Reputation: 45284

Just take a reference to the value.

EmployeeDetail& det = iter->second;   // notice new '&' character.
det.salary = 1000;   // modifies the 'EmployeeDetail' object in-place.

Upvotes: 4

Related Questions