2

I am trying to emplace data into a std::map. Below is what I have tried (trimmed from the original source but definitely gives the idea):

template<typename T> class trie {

private:
std::map<typename T::value_type, std::unique_ptr<trie<T>>> children;
std::unique_ptr<trie<T>> parent;

// Later
public:
trie(const trie<T>& other, trie<T>* const parent) :
parent{parent}
{
    for(auto const &it : other.children)
        children.emplace(it.first, {*it.second});
}

};

The error is as follows:

trie.h: In instantiation of ‘trie<T>::trie(const trie<T>&, trie<T>*) [with T = std::basic_string<char>]’:
main.cpp:7:23:   required from here
trie.h:90:3: error: no matching function for call to ‘std::map<char, std::unique_ptr<trie<std::basic_string<char> >, std::default_delete<trie<std::basic_string<char> > > >, std::less<char>, std::allocator<std::pair<const char, std::unique_ptr<trie<std::basic_string<char> >, std::default_delete<trie<std::basic_string<char> > > > > > >::emplace(const char&, <brace-enclosed initializer list>)’
   children.emplace(it.first, {*it.second});
   ^
trie.h:90:3: note: candidate is:
In file included from /usr/include/c++/4.8.1/map:61:0,
                 from trie.h:4,
                 from main.cpp:2:
/usr/include/c++/4.8.1/bits/stl_map.h:540:2: note: std::pair<typename std::_Rb_tree<_Key, std::pair<const _Key, _Tp>, std::_Select1st<std::pair<const _Key, _Tp> >, _Compare, typename _Alloc::rebind<std::pair<const _Key, _Tp> >::other>::iterator, bool> std::map<_Key, _Tp, _Compare, _Alloc>::emplace(_Args&& ...) [with _Args = {}; _Key = char; _Tp = std::unique_ptr<trie<std::basic_string<char> >, std::default_delete<trie<std::basic_string<char> > > >; _Compare = std::less<char>; _Alloc = std::allocator<std::pair<const char, std::unique_ptr<trie<std::basic_string<char> >, std::default_delete<trie<std::basic_string<char> > > > > >; typename std::_Rb_tree<_Key, std::pair<const _Key, _Tp>, std::_Select1st<std::pair<const _Key, _Tp> >, _Compare, typename _Alloc::rebind<std::pair<const _Key, _Tp> >::other>::iterator = std::_Rb_tree_iterator<std::pair<const char, std::unique_ptr<trie<std::basic_string<char> >, std::default_delete<trie<std::basic_string<char> > > > > >]
  emplace(_Args&&... __args)
  ^
/usr/include/c++/4.8.1/bits/stl_map.h:540:2: note:   candidate expects 0 arguments, 2 provided

So my question is:

How do I correctly initialize the map element, the goal being a deep copy of the pointed-to trie, and no needless copies/moves?

Thanks in advance!

George Hilliard
  • 15,402
  • 9
  • 58
  • 96

2 Answers2

4

By passing {*it.second} as the initialiser for the value, you're effectively trying to initialise a std::unique_ptr<trie<T>> with a trie<T>. I believe you're looking for this:

public:
trie(const trie<T>& other, trie<T>* const parent) :
parent{parent}
{
    for(auto const &it : other.children) {
        // Separate creation of unique_ptr for exception safety, thanks to @DanielFrey
        std::unique_ptr<trie<T>> p(new trie<T>(*it.second));
        children.emplace(it.first, std::move(p));
    }
}

Note that you will also have to provide a copy constructor, because the default one is deleted, as your class has non-copyable members.


Unrelated to the question, but you should reconsider your design: you most likely have an ownership loop. If a trie<T> stores a unique_ptr to its children and these store a unique_ptr back to their parent, you'll get double deletion errors. Turn one of these (probably the pointer to parent) into a raw pointer. Raw pointers are fine for observing without participating in ownership.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • I think you created a resource leak when `emplace` throws. See my answer for what is really needed. – Daniel Frey Oct 13 '13 at 10:09
  • Excellent answer. Good point about the ownership loop; I hadn't thought of that. Could this function serve as the copy constructor if I set `parent = nullptr` in the declaration? Also, is there not a way to construct the `unique_ptr` in-place? I see @DanielFrey's explanation but I figured perhaps I could pass to the constructor with `{}`s. – George Hilliard Oct 13 '13 at 17:45
  • @thirtythreeforty Re copy-ctor: could be, depends on its intended semantics. Would you want to copy the value of `other.parent` in the copy ctor, or not? Re in-place `unique_ptr`: I am afraid it's not really possible, they're all just function calls and it's still possible the construction of `first` will throw before the construction of the `unique_ptr` starts (but the `new` has to be evaluated before `emplace` is called). – Angew is no longer proud of SO Oct 13 '13 at 18:06
4

You need

for(auto const &it : other.children) {
    std::unique_ptr<trie<T>> element(new trie<T>(*it.second));
    children.emplace(it.first, std::move(element));
}

to prevent a resource leak in case an exception is thrown from emplace. If available (C++14), you could simplify the code to

for(auto const &it : other.children) {
    children.emplace(it.first, std::make_unique<trie<T>>(*it.second));
}

As a rule of thumb for all smart pointers, you always use std::make_* or you must use a separate line to create each of them.

Daniel Frey
  • 55,810
  • 13
  • 122
  • 180
  • +1 for the `make_unique` syntax--so much cleaner! Is it faster because the compiler will elide the return value? (Unfortunately the point is somewhat moot, as I'm not going to use a draft standard.) – George Hilliard Oct 13 '13 at 19:21
  • 1
    @thirtythreeforty `make_unique` is actually quite simple and it was a simple oversight that it was not included in C++11. In fact it is so simple, you can write it yourself and add it to your code. You can find it [in this question](http://stackoverflow.com/questions/7038357). It is not really more efficient, but more exception safe as you can see above in my answer. – Daniel Frey Oct 13 '13 at 19:36
  • @thirtythreeforty Just to clarify: Using `make_unique` will most likely be as efficient as an in-place construction as it is just a single pointer and I'm pretty sure the optimizer will be able to remove all overhead. – Daniel Frey Oct 13 '13 at 19:44
  • Oh, ok, thanks for the link. By "faster" I meant, will using `make_unique` directly be more efficient than a `std::move` (even though `make_unique` returns by value) because of copy elision? – George Hilliard Oct 13 '13 at 20:07
  • 1
    @thirtythreeforty Again, the `unique_ptr` is such a small, simple object that there is most likely no "more efficient". It's internally just a single plain pointer which is "moved" by copying it and setting the source to `0` - easy to track and to optimize away for the compiler. The main difference is tracking of the lifetime of that pointer and guaranteeing that `delete` is called in all cases including those involving exceptions. – Daniel Frey Oct 13 '13 at 20:15