1

See this answer for how to insert into without making copies of the map value.

std::map emplace without copying value

Continueing from that answer - suppose my Foo type looks something like this:

struct Foo {
  const int& intref_; 
  std::mutex mutex_;
}

Then initialized using aggregate-initialization like this

Foo{7}

or

Foo{7, std::mutex()}

Would it be somehow possible to be emplaced into the map with type ?:

std::map<size_t, Foo> mymap;

I know I could just write a constructor for Foo - but can it be done with aggregate initialization instead ?

Link to compiler explorer:

https://godbolt.org/z/_Fm4k1

Relevant c++ references:

https://en.cppreference.com/w/cpp/container/map/try_emplace

https://en.cppreference.com/w/cpp/language/aggregate_initialization

darune
  • 10,480
  • 2
  • 24
  • 62
  • Did you try it? What happened when you did? – davidbak Mar 04 '19 at 09:35
  • @davidbak I did try it directly and it doesn't work on any major compiler – darune Mar 04 '19 at 09:36
  • then perhaps a link to a coliru example with the example and compiler error message copied here would make your question better ... just a suggestion – davidbak Mar 04 '19 at 09:39
  • @davidbak I can provide you the small code for the testing - but I do not think it is worth it to provide the compile error I see with a 'naive' approach. – darune Mar 04 '19 at 09:40
  • mutex does not have a copy constructor. Why is the mutex there? Don't you want a new mutex for each object? Do you want all objects to share the same mutex? Or each object have it's own? – KamilCuk Mar 04 '19 at 09:43
  • maybe. compiler errors are usually helpful. did you try the `std::piecewise_construct` overload mentioned in an answer at the linked question: That might get around the problem that mutex is not copyable or movable. – davidbak Mar 04 '19 at 09:43
  • @davidbak I have provided some copy paste test code – darune Mar 04 '19 at 09:43
  • @KamilCuk no - in my case it is fine the struct cannot be copied as a result of having the mutex member - it also has a reference which cannot be default copied either – darune Mar 04 '19 at 09:46
  • thus, piecewise construct? – davidbak Mar 04 '19 at 09:47
  • @davidbak In my example i use the try-emplace and it gives the same behavior as the piecewise construct - just in case I did try it and it didn't change anything. – darune Mar 04 '19 at 09:48
  • You don't upvote the question. What can I do to improve it ? – darune Mar 04 '19 at 09:53
  • are you asking me? i thought i suggested a) providing a link to an online compiler example, and b) providing the error message. it's just my personal preference that C++ questions include that sort of thing as a matter of course. But don't get upset: anyone can vote or not vote for any reason. Right now I'm puzzled why the line `M m{7};` compiles and I'm trying to figure that out. – davidbak Mar 04 '19 at 09:56
  • In fact, I tried the example on wandbox with clang 7 and it warns on the line `M m{7}`: missing field 'm_' initializer [-Wmissing-field-initializers]. So maybe that's broken too. – davidbak Mar 04 '19 at 10:02
  • @davidbak It compiles, because there is a rule that the remaining members gets default initialized. – darune Mar 04 '19 at 10:02
  • ok, idnkt. .... – davidbak Mar 04 '19 at 10:03
  • I'm guessing the answer is that forwarding arguments (e.g., what emplace and try_emplace do) isn't the same as aggregate initialization, and that's that. You've got to have a matching constructor. Hopefully later today some C++ expert will chime in. – davidbak Mar 04 '19 at 10:10

2 Answers2

5

You may exploit casts to indirect your construction

template<typename T>
struct tag { using type = T; };

template<typename F>
struct initializer
{
    F f;
    template<typename T>
    operator T() &&
    {
        return std::forward<F>(f)(tag<T>{});
    }
};

template<typename F>
initializer(F&&) -> initializer<F>;

template<typename... Args>
auto initpack(Args&&... args)
{
    return initializer{[&](auto t) {
        using Ret = typename decltype(t)::type;
        return Ret{std::forward<Args>(args)...};
    }};
}

And use it as

struct Foo
{
  const int& intref_; 
  std::mutex mutex_;
};

void foo()
{
    int i = 42;
    std::map<int, Foo> m;
    m.emplace(std::piecewise_construct,
              std::forward_as_tuple(0),
              std::forward_as_tuple(initpack(i)));
}

Note you can't prolong a temporary's lifetime by binding it to a non-stack reference.

Passer By
  • 19,325
  • 6
  • 49
  • 96
4

It's not insomuch a problem with std::map::try_emplace, as it is with std::pair. As this simple declaration will reproduce an error rooted in the same problem:

std::pair<const int, Foo> p(
    std::piecewise_construct,
    std::forward_as_tuple(0),
    std::forward_as_tuple(i)
);

And it's not really a problem with std::pair alone. As the abstract of n4462 details, it's pretty prevalent. Simply put, that pair c'tor (as do many library functions) does its forwarding like this:

second(std::forward<_Args2>(std::get<_Indexes2>(__tuple2))...)

So no curly braces, and as such no aggregate initialization in C++17, only value initialization. Your only options are to define an actual c'tor, or use something like Passer By's clever solution.

If you can use C++20, then (p0960) was accepted, which allows initializing an aggregate from a parenthesized list of values. This addresses n4462, and allows user code to perfect forward initializers for aggregate too.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458