4

In the C++ standard library allocators can be rebound.

std::allocator<int>::rebind<double>::other gives std::allocator<double>.

Is there an easy way to do this with containers?

The reason is that I have a function that multiplies containers elements by a scalar.

template<class Container, typename S>
auto elementwise_multiply_by_scalar(Container const& c, S s){
    Container ret(c.size());
    for(auto& e : ret) e *= s;
    return ret;
}

But if e * s is not the same type as e, I want to do this:

template<class Container, typename S>
auto elementwise_multiply_by_scalar(Container const& c, S s){
    Container::rebind<decltype(Container::value_type{}*s)> ret(c.size());
    auto it2 = ret.begin();
    for(auto it1 = c.begin(); it1 != c.end(); ++it1, ++it2) *it2 = s * *it1;
    return ret;
}

(I guess it is not clear why I would want the output to be the same class of container as the input, but I guess that is the expected result.)

I could use template template arguments (as in template<template<typename> class V>) but it seems to have its problems too.

Do we need some sort of container_traits<V>::rebind? (e.g. container_traits<std::vector<double, myallocator<double> >>::rebind<int>::other -> std::vector<int, myallocator<double>::rebind<int>::other> )

alfC
  • 14,261
  • 4
  • 67
  • 118
  • The template parameter - is intrinsict part of the container's type, i.e. `vector` is unrelated to `vector`, and the function template is instantiated with the types passed to it. Note that `allocator::rebind` returns a new instance of an allocator of a different type, whereas you expect an existing instance to change its type. (note: it would be possible, in principle, to write a container class that can be rebound to a different type, but it would only encapsulate copying to a new container when the type changes ("significantly")) – peterchen May 13 '18 at 07:27
  • 2
    Use `std::transform` and call it a day – Passer By May 13 '18 at 07:30
  • 1
    @PasserBy, yes, I could implement the loop with `std::transform` but it wouldn't solve the problem. – alfC May 13 '18 at 07:35
  • [`std::vector >`](http://en.cppreference.com/w/cpp/utility/variant) might be a way to achieve this but I must admit I've no experience with `std::variant`. – Scheff's Cat May 13 '18 at 07:42

1 Answers1

1

Something like this might work:

struct A
{
};

struct B
{
};

struct C
{
};

C operator *(const A&, const B&)
{
    return C();
}

template <typename Container, typename ValueType>
struct ContainerConverter
{
};

template <typename SourceValueType, typename ValueType>
struct ContainerConverter<std::vector<SourceValueType>, ValueType>
{
    typedef typename std::vector<ValueType> result_type;
};

template <typename Container, typename S>
auto mult(const Container& c, const S& s)
{
    typedef typename Container::value_type source_element;
    typedef decltype(c.front()*s) result_element;
    typename ContainerConverter<Container, result_element>::result_type result;
    std::transform(c.begin(), c.end(), std::back_inserter(result), [&](const source_element& e){ return e * s; } );
    return result;
}

int main()
{
    std::vector<A> a;
    std::vector<C> c = mult(a, B());
}

You would need to specialise ContainerConverter for each container type that you want to support.

If your containers don't all support std::back_inserter see std::transform to arbitrary container.

Alan Birtles
  • 32,622
  • 4
  • 31
  • 60