在C ++中像Python一样的动态参数解压缩?

时间:2019-05-07 16:25:45

标签: c++

我有一个std::map<int, std::vector<MyClass>>和一个函数,该函数接受动态数量的迭代器,尤其是iter::zip_longest

我想将地图中的矢量传递给zip_longest,但是鉴于地图中有动态数量的矢量,我没有看到实现此目的的直接方法。我知道使用模板和sizeof编译时间常数是可行的,但是我不能在编译时指定。

我本质上想做的是

std::vector<std::vector<MyClass>*> all_vectors;
for (const auto it : my_map) {
  all_vectors.push_back(&it.second);
}
for (const auto it : iter::zip_longest(*all_vectors)) {  // <-- Here using *all_vectors to unpack like in Python.
  // Do stuff here
}

我还要说,我知道可以通过zip_longest以外的其他方式来实现这一点,我只是想知道是否有一种干净的方法可以使用现有功能。

2 个答案:

答案 0 :(得分:3)

C ++是一种静态类型的语言。 zip_longest是一个函数,它根据获取的参数数量生成一个zip迭代器。但是函数获得的参数数量在C ++中是 static 数量。如果需要为函数提供运行时数量的值,则必须提供运行时范围或容器或类似的东西。而zip_longest并不是为此而构建的。

答案 1 :(得分:2)

C ++的静态类型系统是静态的。该向量中有动态数量的元素。因此,您不能让C ++的静态类型系统来处理结果,除非有些花哨的“生成最多N个静态类型然后分派给它”是相当低效的,而且并不是那么费劲。

zip_longest 不是一个功能。它是一个功能模板。函数模板通常是在调用站点上通过静态检查其参数来生成函数。

现在,很有可能创建一个迭代器,给定容器的动态向量,该迭代器将返回元素的动态向量。 source参数中的动态信息(元素数量)与返回类型中的动态元素相对应。

所以这样的事情是可能的:

std::vector<std::vector<MyClass>*> all_vectors;
for (const auto it : my_map) {
  all_vectors.push_back(&it.second);
}
for (const auto& elems : vec_zip_longest(all_vectors)) {
  // elems is a vector of boost::optional<std::ref<MyClass*>>
  // Do stuff here
}

在仅for(:)循环的受限情况下执行此操作看起来像:

template<class C>
struct vec_zip_longest {
  using elem_iterator = decltype( std::begin( std::declval<C&>() ) );
  using elem = std::decay_t< decltype( *std::declval<elem_iterator&>() ) >;
  std::vector< std::optional<elem_iterator> > current;
  std::vector< elem_iterator > finish;

    vec_zip_longest( std::vector<C>& vec ) {
        for (auto&& c : vec )
        {
            current.emplace_back( std::begin(c) );
            finish.emplace_back( std::end(c) );
            if (current.back() == finish.back())
                current.back() = {};
        }
    }

  bool advance() {
    bool retval = false;
    for (std::size_t i = 0; i < current.size(); ++i) {
      auto& it = current[i];
      //std::cerr << "advancing\n";
      if (!it)
      {
        //std::cerr << "already done\n";
        continue;
      }
      ++*it;
      if (*it == finish[i]) {
        //std::cerr << "reached end\n";
        it = std::nullopt;
        continue;
      }
      //std::cerr << "advanced\n";
      retval = true;
    }
    return retval;
  }
  struct iterator {
    vec_zip_longest* parent = nullptr;
    friend bool operator==(iterator const& lhs, iterator const& rhs) {
      return lhs.parent == rhs.parent;
    }
    friend bool operator!=(iterator const& lhs, iterator const& rhs) {
      return lhs.parent != rhs.parent;
    }
    void operator++()& {
      if(!parent->advance())
        parent = nullptr;
    }
    auto operator*() {
      std::vector<std::optional<std::reference_wrapper<elem>>> retval;
      retval.reserve(parent->current.size());
      for(auto&& oit:parent->current) {
        //std::cerr << "getting element\n";
        if (oit) {
          //std::cerr << "something there\n";
          retval.emplace_back(**oit);
        } else {
          //std::cerr << "nothing there\n";
          retval.emplace_back();
        }
      }
      return retval;
    }
  };
  iterator begin() {
    return {this};
  }
  iterator end() {
    return {};
  }
};
template<class C>
vec_zip_longest(std::vector<C>&)->vec_zip_longest<C>;

Live example中。

“更合适的”迭代器将花费更多的样板,并且从技术上讲,您不能使其比输入迭代器更强大。另外,对非成员begin / end的支持也需要我做更多的工作。

测试代码:

std::vector<std::vector<int>> foo{ {1,2,3}, {4,5}, {6} };
vec_zip_longest zip { foo };
for (auto&& c:zip)
{
    for (auto&& e : c ) {
        if (e)
            std::cout << *e << ",";
    }
    std::cout << "\n";
}

输出为:

1,4,6,
2,5,
3,