ankur katiyar
ankur katiyar

Reputation: 101

How to use emplace in map for custom class?

Here is my custom class, which I am using as a key. Next is the value, which I am using as vector. While I am trying to use uniform initialization, I get the compilation error. I am unable to understand what that error really means? Here is the error and the code.

Based on the advise by somebody on insert - How to use insert_or_assign in map for custom class?

I am using the similar thing for emplace(). However, it is not working.

The prototype of emplace says -

https://en.cppreference.com/w/cpp/container/map/emplace
template< class... Args >
std::pair<iterator,bool> emplace( Args&&... args );

It means the Args && is nothing but rvalue reference of class Args right?

In my case, the arg is nothing but std::vector. How can I do emplace?

class MyData
{
    private:
        int age;
      string name;
   public:
      int getAge() const
      {
         return age;
      }
      string& getName()
      {
        return name; 
      }
      MyData(int age_val, string name_val): age(age_val), name(name_val) 
      {
         cout<<"Constructor invoked"<<endl;
      }
      bool operator <(const MyData &other) const
      {
         return age < other.age;
      }
      MyData(const MyData& other)
      {
         cout<<"Copy constructor invoked"<<endl;
         age = other.age;
         name = other.name;
      }
};

int main(int argc, char **argv)
{
   std::map<MyData, vector<string>> studentClasses;
   studentClasses.emplace({32, "SJ"s}, std::vector{"working"s});
   
    return 0;
}



In function ‘int main(int, char**)’:
map.cpp:62:63: error: no matching function for call to ‘std::map<MyData, std::vector<std::__cxx11::basic_string<char> > >::emplace(<brace-enclosed initializer list>, std::vector<std::__cxx11::basic_string<char> >)’
   62 |    studentClasses.emplace({32, "SJ"s}, std::vector{"working"s});
   
   574 |  emplace(_Args&&... __args)
      |  ^~~~~~~
/usr/include/c++/9/bits/stl_map.h:574:2: note:   candidate expects 0 arguments, 2 provided

Upvotes: 3

Views: 1897

Answers (2)

songyuanyao
songyuanyao

Reputation: 173024

It means the Args && is nothing but rvalue reference of class Args right? In my case, the arg is nothing but std::vector. How can I do emplace?

Note that both key and value are passed to emplace to constructor the element in map. (BTW Args&& is not rvalue reference but forwarding reference.)

The problem is that the braced-init-list like {32, "SJ"s} doesn't have type, which causes template argument deduction failing when calling emplace.

Non-deduced contexts:

  1. The parameter P, whose A is a braced-init-list, but P is not std::initializer_list, a reference to one (possibly cv-qualified), or (since C++17) a reference to an array:

You can pass a MyData explicitly:

studentClasses.emplace(MyData{32, "SJ"s}, std::vector{"working"s});

Or specify template argument as:

studentClasses.emplace<MyData>({32, "SJ"s}, std::vector{"working"s});

Upvotes: 2

Scheff&#39;s Cat
Scheff&#39;s Cat

Reputation: 20171

I don't need it often but sometimes I need it: a "real" emplace() in a std::map. Most times it causes me some trouble until I get it working but most times I finally got.

My MCVE:

#include <iostream>
#include <map>
#include <string>
#include <vector>

using namespace std::string_literals;

struct MyData {
  int age;
  std::string name;
  
  MyData(int age, std::string name): age(age), name(name)
  {
    std::cout << "MyData::MyData(int, std::string)\n";
  }
  
  MyData(const MyData& myData): age(myData.age), name(myData.name)
  {
    std::cout << "MyData::MyData(const MyData&)\n";
  }
  
  MyData(MyData&& myData) noexcept:
    age(std::move(myData.age)), name(std::move(myData.name))
  {
    std::cout << "MyData::MyData(MyData&&)\n";
  }

  bool operator<(const MyData& myData) const
  {
    return age < myData.age
      || (age == myData.age && name < myData.name);
  }
};

#define DEBUG(...) std::cout << "Exec: " #__VA_ARGS__ << ";\n"; __VA_ARGS__ 

int main()
{
  DEBUG(std::map<MyData, std::vector<std::string>> studentClasses);
  DEBUG(studentClasses.emplace(MyData{32, "SJ"s}, std::vector{"working"s}));
  DEBUG(studentClasses.emplace<MyData>({32, "SJ"s}, std::vector{"working"s}));
  DEBUG(studentClasses.emplace(std::piecewise_construct,
    std::forward_as_tuple(32, "SJ"s),
    std::forward_as_tuple(std::vector{ "working"s })));
}

Output:

Exec: std::map<MyData, std::vector<std::string>> studentClasses;
Exec: studentClasses.emplace(MyData{32, "SJ"s}, std::vector{"working"s});
MyData::MyData(int, std::string)
MyData::MyData(MyData&&)
Exec: studentClasses.emplace<MyData>({32, "SJ"s}, std::vector{"working"s});
MyData::MyData(int, std::string)
MyData::MyData(MyData&&)
Exec: studentClasses.emplace(std::piecewise_construct, std::forward_as_tuple(32, "SJ"s), std::forward_as_tuple(std::vector{ "working"s }));
MyData::MyData(int, std::string)

Demo on coliru

  1. I provided a move constructor. So, the first two emplace versions (copied from songyuanyao's answer) use that one. (Without a move constructor, emplace() would use the copy constructor of MyData instead – as pointed out by the OP.)

  2. I used my last resort piecewise_construct (which already worked as well in C++11). This one does in fact the "real" emplace (i.e. in-place construction).

Further reading: cppreference.com: std::map::emplace()

Upvotes: 4

Related Questions