Std :: convert to arbitrary container

I want to write a generic function that receives container1

with values [a1, .. , an]

and returns another container2

with values [convert(a1), .. , convert(an)]

. If container2

std::vector

, the problem is trivial, std::transform

does exactly what I want. The following function can deal with arbitrary container2

andcontainer1

template<class ToType, class FromType>
ToType convert(const FromType& from)
{
    std::vector<typename ToType::value_type> tmp;
    std::transform(from.begin(), from.end(),
                   std::back_inserter(tmp),
                   [](const typename FromType::value_type& f) {
        return convert<typename ToType::value_type>(f);
    });
    return ToType(tmp.begin(), tmp.end());
}

      

But he adds a copy. Does anyone know how to do better?

+3


source to share


2 answers


Check out this answer before Is it possible to write a C ++ template to test for the existence of a function? ... You can use SFINAE to determine if a function of your target container exists (for example, push_back

or insert

) or if a container exists to insert your container (for example, inserter

or back_inserter

) and behaves accordingly.

Another way is to create a fake iterator:

template <class T, class U>
struct ConvertIterator {
    typedef T dest_type;
    typedef U it_type;

    ConvertIterator(U&& val) : iterator(std::forward<U>(val)) {

    }

    bool operator == (const ConvertIterator &other) const {
        return iterator == other.iterator;
    }

    bool operator != (const ConvertIterator &other) const {
        return iterator != other.iterator;
    }

    dest_type operator * () const {
        return convert<dest_type>(*iterator);
    }

    ConvertIterator<T, U> & operator ++() {
        ++iterator;
        return *this;
    }

    it_type iterator;
};

      



and then:

template<class ToType, class FromType>
ToType convert(const FromType& from)
{
    typedef ConvertIterator<typename ToType::value_type, decltype(from.begin()) > convert_it;

    return ToType(convert_it(from.begin()), convert_it(from.end()));
}

      

+3


source


Here is a function-based iterator converter. This has all the correct typedefs for the forward iterator. We could update it to support all the tag properties of the incoming tether type Base

if we chose:

template<
  class Base,
  class F,
  class R=typename std::result_of<F(decltype(*std::declval<Base const&>()))>::type
>
struct convert_iterator:
  std::iterator<std::forward_iterator_tag,typename std::decay<R>::type>
{
  Base it;
  F f;

  template<class It, class Func>
  convert_iterator(It&&base, Func&&func):it(std::forward<It>(base)),
  // defaulted stuff:
  convert_iterator()=default;
  convert_iterator(convert_iterator const&)=default;
  convert_iterator(convert_iterator &&)=default;
  convert_iterator& operator=(convert_iterator const&)=default;
  convert_iterator& operator=(convert_iterator &&)=default;

  bool operator==(convert_iterator const&other) const {
    return it == other.it;
  }
  bool operator!=(convert_iterator const&other) const { return !(*this==other); }

  // a bit overkill, but rvalue and lvalue overrides for these:
  R operator*() const& {
    return f(*it);
  }
  R operator*() & {
    return f(*it);
  }
  R operator*() const&& {
    return std::move(f)(*std::move(it));
  }
  R operator*() && {
    return std::move(f)(*std::move(it));
  }
  // normal pre-increment:
  convert_iterator& operator++()& {
    ++it;
    return *this;
  }
  // pre-increment when we are guaranteed not to be used again can be done differently:
  convert_iterator operator++()&& {
    return {std::next(std::move(it)), std::forward<F>(f)};
  }
  // block rvalue post-increment like a boss:
  convert_iterator operator++(int)& {
    return {it++, f};
  }
};

      

helper function to create them:

template< class Base, class F >
convert_iterator<typename std::decay<Base>::type,typename std::decay<F>::type>
make_convert_iterator(Base&& b, F&& f) { return {std::forward<Base>(b), std::forward<F>(f)}; }

      



Next, I create a class that handles the transformation. Specialization allows us to send different containers and scalars:

// for scalars:
template<class ToType,class=void>
struct converter {
  template<class FromType>
  ToType operator()(FromType&& from)const{ return std::forward<FromType>(from); }
};

// attempt at SFINAE test for container:
template<class ToContainer>
struct converter<ToContainer, (void)(
  typename std::iterator_traits<
    typename std::decay<decltype(std::begin(std::declval<ToContainer&>())>::type
  >::value_type
)>
{
  using std::begin; using std::end;

  using R=std::iterator_traits<typename std::decay<decltype(begin(std::declval<ToContainer&>()))>::type>::value_type;

  template<class FromType, class T=decltype(*begin(std::declval<FromType>())>
  ToContainer operator()(FromType&& from) const {
    auto sub_convert = [](T&& t)->R{
      return converter<R>{}(std::forward<T>(t));
    };
    return {
      make_convert_iterator(begin(std::forward<From>(from)), sub_convert),
      make_convert_iterator(end(std::forward<From>(from)), sub_convert)
    };
  };
};

      

The action transform function is now a one-liner:

template<class ToType>
ToType convert(FromType&& from)
{
  return converter<ToType>{}(std::forward<FromType>(from));
}

      

0


source







All Articles