4

I want to write universal function that receives container1 with values [a1, .. , an] and returns another container2 with values [convert(a1), .. , convert(an)]. If container2 is std::vector, the problem is trivial, std::transform does exactly what I want. The following function can deal with arbitrary container2 and container1

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 it does addition copy. Does anyone know how to do better?

2 Answers2

3

Check out this answer to Is it possible to write a C++ template to check for a function's existence?. You can use SFINAE to detect if a function of your destination container exists (such as push_back or insert) or if an inserter for your container exists (such as inserter or back_inserter), and behave 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()));
}
Community
  • 1
  • 1
coyotte508
  • 6,974
  • 5
  • 34
  • 58
  • Note: when citing an answer, it is better to directly link the answer, or at the very least give the answerer's name as point of reference, since the rank is dynamic and may change. I will edit your answer to point directly to Xeo's answer (using the "share" link at the bottom) as it is currently the 3rd one, if this is not the one you wanted to point to... then you will have to fix the link. – Matthieu M. Nov 04 '14 at 10:18
  • I'd want to pass in a converter (stateless?) template function object to `ConvertIterator`, but that is annoying. Hmm. Doesn't the above run into the problem that it recursively invokes `convert` with no terminating case? – Yakk - Adam Nevraumont Nov 04 '14 at 15:54
  • @Yakk: I think you can add a third template parameter to `ConvertIterator`, and pass it as the second argument of the constructor (for both the begin and end iterators). `typedef ConvertIterator<... ...="" decltype=""> convert_it;` and `convert_it(from.begin(), transformer)`. For your second question, no, one convert function (the one shown) has two arguments, and it is implied there is another one that takes one argument, and is non-recursive, to convert the value_type. – coyotte508 Nov 04 '14 at 16:00
  • The convert function shown has one argument and two type arguments. It matches `convert(*it)` perfectly with no SFINAE (but the body fails to compile). I would remove `T` and replace it with `F`, have `operator*` return `f(*iterator)` -- the important part isn't the type converted to, but the function (object) that does the conversion? – Yakk - Adam Nevraumont Nov 04 '14 at 16:05
  • My bad, two type arguments instead of one. In my example, just declare another `convert` function with only one type argument, and have it do what you want, and it **will** compile. Your way of doing things works too (and is nicer than my way in the previous comment with 3 type arguments), using `F` or `decltype(f)` in case of a lambda is ok. – coyotte508 Nov 04 '14 at 16:24
0

Here is a function-based converting iterator. It has all the proper typedefs for a forward iterator. We could upgrade it to support all of the tag properties of the incoming Base iterator type 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};
  }
};

a 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 conversion. Specialization lets us dispatch differently for 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 convert function is now a one-liner:

template<class ToType>
ToType convert(FromType&& from)
{
  return converter<ToType>{}(std::forward<FromType>(from));
}
Yakk - Adam Nevraumont
  • 235,777
  • 25
  • 285
  • 465