c ++一般容器容器`flatten`

时间:2017-06-28 01:54:32

标签: c++ metaprogramming

我有兴趣实现容器容器的通用flatten

为了简化,我将使用自己的容器。主要原因是标准容器为分配器接收了一个额外的模板参数,对于我来说编写操作更复杂。因此,将容器Vector<T>List<T>等视为标准容器的混淆,除了它们不需要分配器。

现在,我有一个操作flatten操作,直到以下四个级别:

template <typename T,
      template <typename> class Container1,
      template <typename> class Container2>
List<T> flatten(const Container1<Container2<T>> & c)
{
  List<T> ret;
  for (auto & l : c)
    for (auto & item : l)
      ret.push_back(item);

  return ret;
}

template <typename T,
      template <typename> class Container1,
      template <typename> class Container2,
      template <typename> class Container3>
List<T>
flatten(const Container1<Container2<Container3<T>>> & c)
{
  List<T> ret;
  for (auto & l : c)
    ret.splice(ret.end(), flatten(l));

  return ret;
}

template <typename T,
          template <typename> class Container1,
          template <typename> class Container2,
          template <typename> class Container3,
          template <typename> class Container4>
    List<T> flatten // and so on for more levels ...

我的问题是:

  1. 是否存在更简洁,特别是一般的方式,允许任何数量的级别,可能基于更优雅的元编程,或者可能正在使用宏来编写此操作?
  2. 如果上一个问题是肯定的,那么有人可以总结一下这个问题吗?
  3. 至少将这种技术应用于标准容器的某种方法?正如我所说,标准容器上flatten版本的复杂之处在于这些版本接收分配器作为模板参数。

3 个答案:

答案 0 :(得分:1)

这些方面的一些东西,也许是:

template <typename T>
struct LooksLikeContainer {
  struct Yes{};
  struct No {Yes yes[2];};

  template <typename U>
  static auto test(U* p) ->
    typename std::enable_if<sizeof(std::begin(*p)) != 0, Yes>::type;

  static No test(...);

  static constexpr bool value = sizeof(test(static_cast<T*>(nullptr))) == sizeof(Yes);
};

template <typename In, typename Out>
auto flatten(const In& in, Out* out) ->
  typename std::enable_if<!LooksLikeContainer<In>::value>::type;

template <typename In, typename Out>
auto flatten(const In& in, Out* out) ->
  typename std::enable_if<LooksLikeContainer<In>::value>::type {
  for (auto& el : in) {
      flatten(el, out);
  }
}

template <typename In, typename Out>
auto flatten(const In& in, Out* out) ->
  typename std::enable_if<!LooksLikeContainer<In>::value>::type {
  out->push_back(in);
}

Live demo

答案 1 :(得分:1)

最简单的方法是递归展平容器:

template<typename Container, typename T>
void flatten_impl(Container const& c, List<T>& out)
{
   for(auto const& elem : c) flatten(elem, out);
}

显然,就像任何递归一样,你也需要终止这个递归:

template<typename Elem, typename T>
void flatten_impl(Elem e, List<T>& out)
{
    out.push_back(e);
}

由于这些现在含糊不清,您需要解决歧义:

template<typename Elem, typename T>
void flatten_impl(Elem e, List<T>& out, ...)
{
    out.push_back(e);
}
template<typename Container, typename T>
std::void_t<typename Container::value_type> flatten_impl(Container const& c, List<T>& out)
{
   for(auto const& elem : c) flatten(elem, out);
}

答案 2 :(得分:1)

使用range-v3,您可以使用ranges::view::join,例如:

namespace detail
{
    struct overload_priority_low {};
    struct overload_priority_high : overload_priority_low {};

    template <typename R>
    R flatten(R&& r, overload_priority_low)
    {
        return std::forward<R>(r);
    }

    template <typename R>
    auto flatten(R&& r, overload_priority_high)
    -> decltype(flatten(std::forward<R>(r) | ranges::view::join, overload_priority_high{}))
    {
        return flatten(std::forward<R>(r) | ranges::view::join, overload_priority_high{});
    }

}

template <typename R>
auto flatten(R&& r)
{
    return detail::flatten(std::forward<R>(r), detail::overload_priority_high{});
}

Demo