1

I have instances of std::map<std::string,Foo> where I insert and fill data. The class Foo is default-constructible, default-copyable. Then I do it by this (simple) way:

std::map<std::string,Foo> data;
for(std::string const& key : keys)
{
  assert(data.count(key) == 0); // it's assumed that the keys not already exist
  Foo& foo = data[key];
  foo.fill(blahblah);
}

After refactoring, the class Foo lost the default empty constructor. What are the (simple) alternatives to insert and fill data ? For the moment, I replaced by this but I don't know if it's the best/simplier way to refactor the code:

std::map<std::string,Foo> data;
for(std::string const& key : keys)
{
  assert(data.count(key) == 0); // it's assumed that the keys not already exist
  Foo& foo = data.emplace(key, Foo(requirement)).first->second;
  foo.fill(blahblah);
}

I also think it could be possible with insert, insert_or_assign, emplace, emplace_hint, try_emplace. I'm a little bit lost with all the possibilities...

I'm interested in solutions for C++11 or C++17.

Caduchon
  • 4,574
  • 4
  • 26
  • 67
  • 1
    Use `insert()` instead of `[]`? – Sam Varshavchik Apr 13 '23 at 12:00
  • @SamVarshavchik it looks quite similar if I well understand : `Foo& foo = data.insert(std::make_pair(key, Foo(requirement))).first->second;` or maybe `Foo& foo = data.insert({key, Foo(requirement)}).first->second;` ? Is there a difference between `emplace()` and `insert({})` ? – Caduchon Apr 13 '23 at 12:09
  • Yes, there is a difference, which should be explained in your C++ textbook, which will also explain the important difference between `[]` and `insert()`. Do you have a textbook, or some other reference material that you can consult for all the details? – Sam Varshavchik Apr 13 '23 at 12:11
  • 3
    Unless you have a good reason not to: Go for `try_emplace`, as it's the only of the signatures which will work with objects not permitting move/copy operations. `emplace` and `insert` differ only between `insert` expecting an `std::make_pair` while `emplace` is that already doing for you. – Ext3h Apr 13 '23 at 12:11
  • @SamVarshavchik, of course, (re)-read textbooks is a good reason to remove stackoverflow from the web... My textbook is en.cppreference.com, and it's quite hudge. After reading in details, `insert` will create 2 copies if the provided template types are not exacly the same than the value_type. I prefer `emplace`. – Caduchon Apr 13 '23 at 12:19
  • You have the same textbook as me, then. And just earlier today I needed to look up something about ranges, and that's where I went to look it up instead of asking a question here, because this is not a textbook replacement. Knowing where to look up and how to read technical documentation is, pretty much, a mandatory skill for every C++ developer. And that's not Stackoverflow, which has a different purpose: asking things that are slightly less obvious then something that can be answered simply by looking it up in reference material. – Sam Varshavchik Apr 13 '23 at 12:28
  • @SamVarshavchik I agree. But it's a subjective question. Reading the documentation shows that there is several acceptable solutions. My question requires some experience, this is not (yet?) in documentation, but experience is a foundament of stackoverflow. The existing disscussion threads about that question linked below is a proof that it's not a "read-the-book" decision. – Caduchon Apr 13 '23 at 12:34
  • @Ext3h You can do the same with `emplace`: https://godbolt.org/z/PhPdzWs5W. However, `try_emplace` is much easier to use for multi-arg constructors. – Daniel Langr Apr 13 '23 at 12:48
  • 1
    @Caduchon Does `data.emplace(key, requirement)` work for you? `data.emplace(key, Foo(requirement))` involves unnecessary move/copy constructor call. – Daniel Langr Apr 13 '23 at 12:50
  • @DanielLangr ho yeah ! This works for me. It's an easy way for me, because I don't have to specify the type `Foo` in the call (actually, I simplified: I have a lot of similar classes Foo-like, all requiring to pass `*this` at construction). Then I can replace by `data.emplace(key, *this)` each time. – Caduchon Apr 13 '23 at 13:00

3 Answers3

2

You can use std::map::emplace together with std::move in order to add an element of a class that lacks a default constructor. This can be done after it was initialized with fill(...).

Example:

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

struct Foo
{
    Foo(int n) : m_n(n) {}
    int m_n;
};

int main() 
{
    std::map<std::string, Foo> m;

    // Create a Foo and insert it:
    Foo foo{ 111 };
    // foo.fill(blahblah);
    m.emplace("aaa", std::move(foo));

    // Verify it was inserted properly:
    Foo& foo2 = m.at("aaa");
    std::cout << foo2.m_n << std::endl;
    return 0;
}

Output:

111

Live demo

Note that I used std::map::at to verify that the element was inserted properly (using operator[] will fail to compile due to Foo lacking a default ctor).

wohlstad
  • 12,661
  • 10
  • 26
  • 39
2

I would simply go with:

data.emplace(key, requirement)

or, since C++17, with

data.try_emplace(key, requirement)

Using try_emplace is generally better (see the question mentioned in the other answer); however, ASAIK, in your case, both options are effectively the same.

Daniel Langr
  • 22,196
  • 3
  • 50
  • 93
1

From here

A call to this function is equivalent to:

(*((this->insert(make_pair(k,mapped_type()))).first)).

So the literal replacement for the operator[] in this case would be using insert function. However, I would suggest using emplace in order to avoid additional construction of std::pair, which insert function accepts as argument.

Since C++17, you can use try_emplace except very subtle cases. You can refer to this discussion in order to see if preferring emplace over try_emplace applies to you case.

Karen Baghdasaryan
  • 2,407
  • 6
  • 24