4

I understand unique_ptr can only be move constructed and move assigned, but the following code still puzzles me,

#include <map>
#include <memory>
#include <utility>

using namespace std;

int main()
{
    map<int, unique_ptr<int>> a{}; // 1, OK
    map<int, map<int, unique_ptr<int>>> b{{1, {}}}; // 2, error, copy ctor accessed

    map<int, map<int, unique_ptr<int>>> d; // 3, workaround
    d.try_emplace(1, move(map<int, unique_ptr<int>>{})); // 4 workaround
}

Of line 1 through 4, only line 2 fails to compile, and I think it means somewhere the unique_ptr's copy ctor is accessed.

error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int; _Dp = std::default_delete<int>]’

But I am not trying to instantiate a unique_ptr here at all. I can buy the theory that line 1 is merely calling default ctors, so it is not a good reference to compare, but still, in line 2 I am only instantiating an empty inner map object, which should not instantiate a unique_ptr.

My limited understanding of initializer list is that copy ctor is involved (maybe more than once?) . But again, in this example, shouldn't the innner map be initialized as an empty map therefore unique_ptr's ctor should not be invoked?

Btw my workaround is line 3 and 4, so far they seem to work, but still would like to understand why line 2 fails.

QnA
  • 1,035
  • 10
  • 25
  • GCC 10 on Linux – QnA May 31 '21 at 19:29
  • thinking of it more, I do not need to call the std::move on line 4. – QnA May 31 '21 at 19:31
  • and fwiw, in real code the unique_ptr is pointing to a custom object, rather than int. The error message is the same except the template type. – QnA May 31 '21 at 20:04
  • For reference, you don't need the map-within-a-map to trigger the behavior: https://gcc.godbolt.org/z/7ffGPjjn3. –  May 31 '21 at 20:11
  • @Frank I think we are talking about different things. in your example, `map> a{{1, unique_ptr{}}};`, the pair.second, which is a unique_ptr, will be copy constructed, and that is forbidden. – QnA May 31 '21 at 20:15
  • 1
    isn't `map>` also non-copyable? – apple apple May 31 '21 at 20:23
  • 1
    I think the problem must be that the constructor of `std::map` that takes an `initializer_list` parameter tries to copy entries into the map, and that clearly won't work here, either for version #1 or for version #2. – Paul Sanders May 31 '21 at 20:27
  • @QnA it's as much copy-constructed as your example #2 –  May 31 '21 at 20:34
  • @appleapple, `std::is_copy_constructible_v>>` evaluates to true, now I'm more confused... I though it should be non-copyable too. – QnA May 31 '21 at 20:35
  • @PaulSanders, when you say version #1, did you mean line 1 or something else? since line 1 compiles fine. – QnA May 31 '21 at 20:37
  • 1
    It doesn't if you try to construct `a` with an `initializer_list` as you are doing in version #2, see @Frank's link. – Paul Sanders May 31 '21 at 20:40
  • @Frank, in line 2, I only give it key-value pair for the outter map, and the inner map is empty, so no unique_ptr object is even instantiated in the first place, and no copy should be done, or at least I think... in your example, you are inserting into the inner map directly, hence a unique_ptr is made first, and then copied. maybe there is more to it that I do not understand? – QnA May 31 '21 at 20:42
  • 1
    The code generated by the compiler doesn't care wether the passed map contains any entries or not. It needs to be able to handle any `map>`. Code generation happens based on **type** information, not data. So it's the same situation: initializing a map with a non-copy-constructible **type** in the initializer_list. –  May 31 '21 at 20:43
  • 2
    @QnA as fas as is_copy_constructible goes: https://stackoverflow.com/questions/18404108/false-positive-with-is-copy-constructible-on-vectorunique-ptr –  May 31 '21 at 20:52
  • 1
    Does this answer your question? [initializer\_list and move semantics](https://stackoverflow.com/questions/8193102/initializer-list-and-move-semantics) –  May 31 '21 at 22:27
  • You may also consider use `std::map, std::unique_ptr`, which will be faster in look up. – prehistoricpenguin Jun 01 '21 at 02:10
  • @Frank thanks, the link about is_copy_constructible limitations is quite helpful. – QnA Jun 02 '21 at 13:39
  • @Frank, the initializer list move semantics link is hitting a different note, but I think I understand now what you guys are trying to say. So line 2 is fundamentally different than line 1, in that it requires a copy construct of an (albeit empty) inner map, and that triggers the inner map to build a full-fledged copy constructor, which failed. I would still prefer to understand and verify from the actual library code, but honestly the gcc error messages are a bit overwhelming... – QnA Jun 02 '21 at 13:45
  • @prehistoricpenguin, i see, so flattening the dictionary helps performance because there is only one binary search instead of two? In my case the outer map is actually an unordered_map so I need a customized hash to use pair as key, which I have been avoiding so far... – QnA Jun 02 '21 at 13:50
  • @QnA you've got it! I'll draft an answer that explains a bit how to make sense of it all. –  Jun 02 '21 at 13:50
  • @Frank, sure, thanks! It would be nice to include in the answer a couple excerpts from the actual library code of how this triggering mechanism (from initializer list all the way to unique_ptr copy ctor, maybe via traits or concepts?) works in compile time (which so far I still fail to do). but maybe that's too much to ask for in a busy world:) – QnA Jun 02 '21 at 13:59

0 Answers0