2

Here is an example code:

struct T
{
    T(int x) : x_(x)
    {}

    T(T&&) = delete;
    T(const T&) = delete;

    int x_;
};

int main()
{
    std::unordered_map<int, T> m;
    m.emplace(std::piecewise_construct, std::forward_as_tuple(1), std::forward_as_tuple(2));
    m.emplace(std::piecewise_construct, std::forward_as_tuple(1), std::forward_as_tuple(2));

    return 0;
}

Second emplace fails but T constructor is called twice. I have though that emplace will construct an object only when insertion is possible. Could you explain it ?

Edit: I use Visual Studio 2017 compiler.

Irbis
  • 1,432
  • 1
  • 13
  • 39
  • related/dupe: https://stackoverflow.com/questions/23704706/stdunordered-mapemplace-object-creation – NathanOliver Jan 27 '20 at 15:48
  • Also see: https://stackoverflow.com/questions/26446352/what-is-the-difference-between-unordered-map-emplace-and-unordered-map-ins – NathanOliver Jan 27 '20 at 15:48
  • Are you sure? This shows only one call of constructor: https://wandbox.org/permlink/pAoaakc0p6ZyN2Xf Maybe you should add information about compiler inst version and system? – Marek R Jan 27 '20 at 15:59
  • @MarekR `int main() 1: 1 T::T(int) x_: 2 int main() 2: 2 T::T(int) x_: 2` equals 2 constructors called, no? – NathanOliver Jan 27 '20 at 16:02
  • @NathanOliver for a second I misunderstood his question (as last emplace performs two constructions) since he didnn't phrased it very well. Anyway I'm voting to mark this as duplicate as you have suggested (do not know why you didn't do it yourself). – Marek R Jan 27 '20 at 16:10
  • @MarekR neither of two links by NathanOliver appears to be a duplicate of this question. – ALX23z Jan 27 '20 at 16:28
  • 1
    It shouldn't call the constructor twice. It is probably an error of STL implementation that you use. In most cases this wouldn't be a problem and it would probably be optimized away, but still this shouldn't happen. Consider sending it as a bug report to compiler that you use. – ALX23z Jan 27 '20 at 16:30
  • I use Visual Studio 2017 compiler and if it is an compiler bug this question shouldn't be marked as duplicate. – Irbis Jan 27 '20 at 16:36
  • It is an exact dupe. Other question complains `I was wondering is there a good rationale for why emplace creates an object that it later disregards?` and quotes standard: `Inserts a value_type object t constructed with std::forward(args)... if and only if there is no element in the container with key equivalent to the key of t.`. Accepted answer says standard is not very precise in this matter. – Marek R Jan 27 '20 at 16:46

1 Answers1

4

From cppreference:

The element may be constructed even if there already is an element with the key in the container, in which case the newly constructed element will be destroyed immediately.

The reason for this behavior is that the container needs to construct a key object to be able to check whether it is already present; the mapped object must be constructed at the same time since they are members of the same object (the value_type pair).

try_emplace (since C++17) is a better option in this case, since it will only construct the mapped object if insertion succeeds. It is able to do this since it takes the key as the first argument, and emplaces the mapped object from the remaining arguments, resulting in a far nicer interface:

m.try_emplace(1, 2);
m.try_emplace(1, 2);
              ^ key (copied or moved)
                 ^ mapped_type emplacement args (forwarded)
ecatmur
  • 152,476
  • 27
  • 293
  • 366