The Std::Transform-Like Function That Returns Transformed Container

The std::transform-like function that returns transformed container

Simplest cases: matching container types

For the simple case where the input type matches the output type (which I've since realized is not what you're asking about) go one level higher. Instead of specifying the type T that your container uses, and trying to specialize on a vector<T>, etc., just specify the type of the container itself:

template <typename Container, typename Functor>
Container transform_container(const Container& c, Functor &&f)
{
Container ret;
std::transform(std::begin(c), std::end(c), std::inserter(ret, std::end(ret)), f);
return ret;
}

More complexity: compatible value types

Since you want to try to change the item type stored by the container, you'll need to use a template template parameter, and modify the T to that which the returned container uses.

template <
template <typename T, typename... Ts> class Container,
typename Functor,
typename T, // <-- This is the one we'll override in the return container
typename U = std::result_of<Functor(T)>::type,
typename... Ts
>
Container<U, Ts...> transform_container(const Container<T, Ts...>& c, Functor &&f)
{
Container<U, Ts...> ret;
std::transform(std::begin(c), std::end(c), std::inserter(ret, std::end(ret)), f);
return ret;
}

What of incompatible value types?

This only gets us partway there. It works fine with a transform from signed to unsigned but, when resolving with T=int and U=std::string, and handling sets, it tries to instantiate std::set<std::string, std::less<int>, ...> and thus doesn't compile.

To fix this, we want to take an arbitrary set of parameters and replace instances of T with U, even if they are the parameters to other template parameters. Thus std::set<int, std::less<int>> should become std::set<std::string, std::less<std::string>>, and so forth. This involves some custom template meta programming, as suggested by other answers.

Template metaprogramming to the rescue

Let's create a template, name it replace_type, and have it convert T to U, and K<T> to K<U>. First let's handle the general case. If it's not a templated type, and it doesn't match T, its type shall remain K:

template <typename K, typename ...>
struct replace_type { using type = K; };

Then a specialization. If it's not a templated type, and it does match T, its type shall become U:

template <typename T, typename U>
struct replace_type<T, T, U> { using type = U; };

And finally a recursive step to handle parameters to templated types. For each type in a templated type's parameters, replace the types accordingly:

template <template <typename... Ks> class K, typename T, typename U, typename... Ks>
struct replace_type<K<Ks...>, T, U>
{
using type = K<typename replace_type<Ks, T, U>::type ...>;
};

And finally update transform_container to use replace_type:

template <
template <typename T, typename... Ts> class Container,
typename Functor,
typename T,
typename U = typename std::result_of<Functor(T)>::type,
typename... Ts,
typename Result = typename replace_type<Container<T, Ts...>, T, U>::type
>
Result transform_container(const Container<T, Ts...>& c, Functor &&f)
{
Result ret;
std::transform(std::begin(c), std::end(c), std::inserter(ret, std::end(ret)), f);
return ret;
}

Is this complete?

The problem with this approach is it is not necessarily safe. If you're converting from Container<MyCustomType> to Container<SomethingElse>, it's likely fine. But when converting from Container<builtin_type> to Container<SomethingElse> it's plausible that another template parameter shouldn't be converted from builtin_type to SomethingElse. Furthermore, alternate containers like std::map or std::array bring more problems to the party.

Handling std::map and std::unordered_map isn't too bad. The primary problem is that replace_type needs to replace more types. Not only is there a T -> U replacement, but also a std::pair<T, T2> -> std::pair<U, U2> replacement. This increases the level of concern for unwanted type replacements as there's more than a single type in flight. That said, here's what I found to work; note that in testing I needed to specify the return type of the lambda function that transformed my map's pairs:

// map-like classes are harder. You have to replace both the key and the key-value pair types
// Give a base case replacing a pair type to resolve ambiguities introduced below
template <typename T1, typename T2, typename U1, typename U2>
struct replace_type<std::pair<T1, T2>, std::pair<T1, T2>, std::pair<U1, U2>>
{
using type = std::pair<U1, U2>;
};

// Now the extended case that replaces T1->U1 and pair<T1,T2> -> pair<T2,U2>
template <template <typename...> class K, typename T1, typename T2, typename U1, typename U2, typename... Ks>
struct replace_type<K<T1, T2, Ks...>, std::pair<const T1, T2>, std::pair<const U1, U2>>
{
using type = K<U1, U2,
typename replace_type<
typename replace_type<Ks, T1, U1>::type,
std::pair<const T1, T2>,
std::pair<const U1, U2>
>::type ...
>;
};

What about std::array?

Handling std::array adds to the pain, as its template parameters cannot be deduced in the template above. As Jarod42 notes, this is due to its parameters including values instead of just types. I've gotten partway by adding specializations and introducing a helper contained_type that extracts T for me (side note, per Constructor this is better written as the much simpler typename Container::value_type and works for all types I've discussed here). Even without the std::array specializations this allows me to simplify my transform_container template to the following (this may be a win even without support for std::array):

template <typename T, size_t N, typename U>
struct replace_type<std::array<T, N>, T, U> { using type = std::array<U, N>; };

// contained_type<C>::type is T when C is vector<T, ...>, set<T, ...>, or std::array<T, N>.
// This is better written as typename C::value_type, but may be necessary for bad containers
template <typename T, typename...>
struct contained_type { };

template <template <typename ... Cs> class C, typename T, typename... Ts>
struct contained_type<C<T, Ts...>> { using type = T; };

template <typename T, size_t N>
struct contained_type<std::array<T, N>> { using type = T; };

template <
typename Container,
typename Functor,
typename T = typename contained_type<Container>::type,
typename U = typename std::result_of<Functor(T)>::type,
typename Result = typename replace_type<Container, T, U>::type
>
Result transform_container(const Container& c, Functor &&f)
{
// as above
}

However the current implementation of transform_container uses std::inserter which does not work with std::array. While it's possible to make more specializations, I'm going to leave this as a template soup exercise for an interested reader. I would personally choose to live without support for std::array in most cases.

View the cumulative live example


Full disclosure: while this approach was influenced by Ali's quoting of Kerrek SB's answer, I didn't manage to get that to work in Visual Studio 2013, so I built the above alternative myself. Many thanks to parts of Kerrek SB's original answer are still necessary, as well as to prodding and encouragement from Constructor and Jarod42.

std::transform to arbitrary container

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()));
}

STL algorithm similar to transform which allows access to previously transformed element, similar to accumulate

You can use std::transform and pass a lambda that captures your output container.

std::transform(std::begin(input)
, std::end(input)
, std::back_inserter(output)
, [&](auto& input) {
if (output.empty()) {
return transform_input(input, {});
} else {
return transform_input(input, output.back());
}
);

std::transform with lambda: skip some items

template<class Src, class Sink, class F>
void transform_if(Src&& src, Sink&& sink, F&& f){
for(auto&& x:std::forward<Src>(src))
if(auto&& e=f(decltype(x)(x)))
*sink++ = *decltype(e)(e);
}

Now simply get a boost or std or std experiental optional. Have your f return an optional<blah>.

auto sink = std::inserter(first_to_last_name_map, first_to_last_name_map.begin());
using pair_type = decltype(first_to_last_name_map)::value_type;

transform_if(names, sink,
[](const std::string& i)->std::optional<pair_type>{
if (i == "bad")
return {}; // Don't Want This
else
return std::make_pair(i.substr(0,5), i.substr(5,5));
}
);

My personal preferred optional actually has begin end defined. And we get this algorithm:

template<class Src, class Sink, class F>
void polymap(Src&& src, Sink&& sink, F&& f){
for(auto&& x:std::forward<Src>(src))
for(auto&& e:f(decltype(x)(x)))
*sink++ = decltype(e)(e);
}

which now lets the f return a range, where optional is a model of a zero or one element range.

Passing output of c++ transform to a function

If your function f takes a container, then you will have to create a container with the transformed values. In your example you seem to also expect that X remains unchanged by the first transformation, so you you cannot just modify it inplace

returnVal1 = f( X );
transform( X.begin(), X.end(), X.begin(),
[]( double val ){ return val + 0.1*val; } );
returnVal2 = f( X );

If you don't want to create explicit local temporaries then you can construct them using a helper function.

// pass in by value to get a copy, modify it inplace and return it
array2Drow transformed( array2Drow copy, double factor )
{
std::transform( copy.begin(), copy.end(), copy.begin(),
[&]( double val ){ return val + factor*val; } ) );
return copy;
}

The calculation of the return values can then be written using the helper.

returnVal1 = f( X );
returnVal2 = f( transformed( X, 0.1 ) );
returnVal3 = f( transformed( X, -0.1 ) );

Templating a function to deduce the return type stl-container from input arguments

I'm answering because it seems that the hinting people are doing in the comments isn't helping you out.

For vector-containers (e.g., vector and list, and not map) that have a single type associated with them (e.g. vector<int>), you can accept a variadic template template parameter to match the container, but you must also remember that the template definitions for these containers often contain things other than the type. For example, an Allocator (which is rarely used, but allows you to have some control over memory allocation).

We forget about the Allocator etc. often because these are usually defaulted.

Here's your new interface:

template <template<class...> class Container, class... Args>
Container<double> transform(const Container<int, Args...>& container);

You were explicit about matching a container of int and transforming to a container of double, so I replicated that here.

The Args... will match the Allocator (and any other template arguments after the type). We are able to ignore specifying the Allocator in the return type because recall that it, and other arguments after it, are usually defaulted.

Now you can call it like so:

// Using vectors
std::vector<int> data_vector_in{0, 1};
auto data_vector_out = transform(data_vector_in);

// ensure we got the right type back
static_assert(std::is_same<decltype(data_vector_out), std::vector<double>>::value, "Err");

// Using lists
std::list<int> data_list_in{0, 1};
auto data_list_out = transform(data_list_in);
static_assert(std::is_same<decltype(data_list_out), std::list<double>>::value, "Err");

Live Demo (C++14)

constructing a std::vector using std::transform. Possibility to return unnamed result?

Yes it is possible, and quite simple when using boost:

struct A
{
};

struct B
{
};

std::vector<B> Convert(const std::vector<A> &input)
{
auto trans = [](const A&) { return B{}; };
return { boost::make_transform_iterator(input.begin(), trans), boost::make_transform_iterator(input.end(), trans) };
}

https://wandbox.org/permlink/ZSqt2SbsHeY8V0mt

But as other mentioned this is weird and doesn't provide any gain (no performance gain or readability gain)

Construct new container from transformed range

You can use boost::adaptors::transformed. The docs state that the input range MUST at least be SinlgePassRange, but also says:

  • Returned Range Category: The range category of rng.

SO if the input range is random-access the output range will be, too. This removes your worry.



Related Topics



Leave a reply



Submit