0

Is the implementation of the erase function of std::set on msvc v140 not conforming standard C++11?

According to the standard (and as mentioned here) "the erase members shall invalidate only iterators and references to the erased elements." However some code that relies on this doesn't work as expected (below an example resulting in memory leaks, actual code runs forever despite being a much smaller set).

#include <set>
#include <random>
#include <iostream>
#include <algorithm>

#define MSVC_IS_NOT_STANDARD_COMPLIANT

int main() {
  // Attempt memory debugging
  _CrtSetDbgFlag(_CRTDBG_LEAK_CHECK_DF | _CRTDBG_DELAY_FREE_MEM_DF | _CRTDBG_CHECK_ALWAYS_DF | _CRTDBG_CHECK_CRT_DF | _CRTDBG_LEAK_CHECK_DF);

  std::cout << "Generating numbers" << std::endl;
  using Numbers = std::set<unsigned short>;
  Numbers numbers;
  std::mt19937_64 generator;
  std::uniform_int_distribution<unsigned> distr(0, std::numeric_limits<unsigned short>::max());
  for (int i = 0; i < 100000; ++i) {
    numbers.emplace(distr(generator));
  }

  std::cout << "Determining non-primes" << std::endl;
  auto is_prime = [](unsigned short n) {
    if (n<3) {
      return true;
    }
    for (unsigned i =2;i<n;++i) {
      if (n % i == 0) {
        return false;
      }
    }
    return true;
  };

  std::vector<Numbers::const_iterator> to_remove;
  for (auto it = numbers.begin(); it != numbers.end(); ){
    if (!is_prime(*it)) {
#ifdef MSVC_IS_NOT_STANDARD_COMPLIANT
      to_remove.emplace_back(it);
    }
    ++it;
#else
      it = numbers.erase(it);
    } else {
      ++it;
    }
#endif
}

#ifdef MSVC_IS_NOT_STANDARD_COMPLIANT
  std::cout << "Removing non-primes" << std::endl;
  for (auto it : to_remove) {
    numbers.erase(it);
  }
#endif

  std::cout << "Number of primes in [0-65536]:" << numbers.size() << std::endl;

Please note that I would rather prefer that there is a bug in the actual code (I can't reproduce the same behavior with a toy example) or that I don't understand the standard.

But just in case of non conformance: Is there an overview of the issues in standard compliance of the msvc versions?
My first thought was that this requirement was dropped in later versions of the standard and that the msvc implementation was ahead. However this requirement is still there in later versions (C++11/23.2.5.13, C++14/23.2.4.9, c++18/26.2.7.14). Given that removing entries from a set is quite common, I'm wondering what other issues the msvc implementation has (regarding the standard compliance) and how this changes over time (hoping to move to msvc 14.2 soon).

boojum
  • 181
  • 2
  • 15
  • You can also ask Microsoft about their implementation : https://github.com/microsoft/STL – Robert Andrzejuk Nov 13 '19 at 08:49
  • btw your macro has a confusing name. It is called `MSVC_IS_STANDARD_COMPLIANT` but when defined you compile code that assumes you need a workaround for msvc being not standard compliant. – 463035818_is_not_an_ai Nov 13 '19 at 09:01
  • would be goodif you include a bit more information on how the code fails, because as pointed out there is a problem in your code, but I wouldnt know how that can lead to a memory leak – 463035818_is_not_an_ai Nov 13 '19 at 09:04
  • @rafix07: sorry fixed that this doesn't influence the UB – boojum Nov 13 '19 at 10:24
  • @RobertAndrzejuk: I'll do that, i have checked it and it doesn't work in VS2019 – boojum Nov 13 '19 at 10:24
  • 1
    You have infinive loop now. If `MSVC_IS_NOT_STANDARD_COMPLIANT` is defined, and isPrime returns true, `it` is not incremented. – rafix07 Nov 13 '19 at 10:33
  • So, is the code as currently shown expected to exhibit some kind of undesired behavior? It [seems to be working](https://rextester.com/RAQJ81023) for me just fine. – Igor Tandetnik Nov 14 '19 at 04:35
  • @Igor Tandetnik When the example is compiled in debug it prints memory leaks. The real application code just hangs. So then I noticed the, for me, quite unusual pattern of gathering iterators and then removing them in batch. According to the first link above, this is allowed for set but there appears to be some problems (see comments). I have rewritten the real application and now it doesn't hang anymore. So this leaves me with the possibility of a bug in the application that doesn't show or a faulty implementation of set::erase. Given my track record on this example the former seems likely :) – boojum Nov 14 '19 at 05:13

0 Answers0