void c_Set::clear() {
  ++m_version;
  dropImmCopy();
  decRefArr(arrayData());
  m_arr = staticEmptyMixedArray();
  m_size = 0;
  setIntLikeStrKeys(false);
}
typename std::enable_if<
  std::is_base_of<BaseSet, TSet>::value, Object>::type
BaseSet::php_map(const Variant& callback) const {
  VMRegGuard _;
  CallCtx ctx;
  vm_decode_function(callback, nullptr, false, ctx);
  if (!ctx.func) {
    SystemLib::throwInvalidArgumentExceptionObject(
      "Parameter must be a valid callback");
  }
  auto set = req::make<TSet>();
  if (!m_size) return Object{std::move(set)};
  assert(posLimit() != 0);
  assert(hashSize() > 0);
  assert(set->arrayData() == staticEmptyMixedArray());
  auto oldCap = set->cap();
  set->reserve(posLimit()); // presume minimum collisions ...
  assert(set->canMutateBuffer());
  constexpr int64_t argc = useKey ? 2 : 1;
  TypedValue argv[argc];
  for (ssize_t pos = iter_begin(); iter_valid(pos); pos = iter_next(pos)) {
    auto e = iter_elm(pos);
    TypedValue tvCbRet;
    int32_t pVer = m_version;
    if (useKey) {
      argv[0] = e->data;
    }
    argv[argc-1] = e->data;
    g_context->invokeFuncFew(&tvCbRet, ctx, argc, argv);
    // Now that tvCbRet is live, make sure to decref even if we throw.
    SCOPE_EXIT { tvRefcountedDecRef(&tvCbRet); };
    if (UNLIKELY(m_version != pVer)) throw_collection_modified();
    set->addRaw(&tvCbRet);
  }
  // ... and shrink back if that was incorrect
  set->shrinkIfCapacityTooHigh(oldCap);
  return Object{std::move(set)};
}
ALWAYS_INLINE
typename std::enable_if<
  std::is_base_of<BaseMap, TMap>::value, Object>::type
BaseMap::php_map(const Variant& callback) const {
  VMRegGuard _;
  CallCtx ctx;
  vm_decode_function(callback, nullptr, false, ctx);
  if (!ctx.func) {
    SystemLib::throwInvalidArgumentExceptionObject(
               "Parameter must be a valid callback");
  }
  auto map = req::make<TMap>();
  if (!m_size) return Object{std::move(map)};
  assert(posLimit() != 0);
  assert(hashSize() > 0);
  assert(map->arrayData() == staticEmptyMixedArray());
  map->m_arr = MixedArray::asMixed(MixedArray::MakeReserveMixed(cap()));
  map->setIntLikeStrKeys(intLikeStrKeys());
  wordcpy(map->hashTab(), hashTab(), hashSize());
  {
    uint32_t used = posLimit();
    int32_t version = m_version;
    uint32_t i = 0;
    // When the loop below finishes or when an exception is thrown,
    // make sure that posLimit() get set to the correct value and
    // that m_pos gets set to point to the first element.
    SCOPE_EXIT {
      map->setPosLimit(i);
      map->arrayData()->m_pos = map->nthElmPos(0);
    };
    constexpr int64_t argc = useKey ? 2 : 1;
    TypedValue argv[argc];
    for (; i < used; ++i) {
      const Elm& e = data()[i];
      Elm& ne = map->data()[i];
      if (isTombstone(i)) {
        ne.data.m_type = e.data.m_type;
        continue;
      }
      TypedValue* tv = &ne.data;
      if (useKey) {
        if (e.hasIntKey()) {
          argv[0].m_type = KindOfInt64;
          argv[0].m_data.num = e.ikey;
        } else {
          argv[0].m_type = KindOfString;
          argv[0].m_data.pstr = e.skey;
        }
      }
      argv[argc-1] = e.data;
      g_context->invokeFuncFew(tv, ctx, argc, argv);
      if (UNLIKELY(version != m_version)) {
        tvRefcountedDecRef(tv);
        throw_collection_modified();
      }
      if (e.hasStrKey()) {
        e.skey->incRefCount();
      }
      ne.ikey = e.ikey;
      ne.data.hash() = e.data.hash();
      map->incSize();
      // Needed so that the new elements are accounted for when GC scanning.
      map->incPosLimit();
    }
  }
  return Object{std::move(map)};
}