Otto Nahmee Otto Nahmee - 1 year ago 45
C++ Question

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

I have a function where the objective is to take an stl-container of a specific data type and return the same stl-container of a different specific data type. See below:

template <
template <typename, typename = std::allocator<double> > class ReturnContainer,
template <typename, typename = std::allocator<int> > class Container
>
inline ReturnContainer<double> transform(const Container<int>& container)
{
ReturnContainer<double> ret(container.size());

for(size_t i = 0; i < container.size(); ++i)
{
// Do something here
}
return ret;
}


Which you could use as:

//
// Using vectors
//
std::vector<int> data_vector_in;
data_vector_in.push_back(0.0);
data_vector_in.push_back(1.0);

std::vector<double> data_vector_out = transform<std::vector>(data_vector_in);

//
// Using lists
//
std::list<int> data_list_in;
data_list_in.push_back(0.0);
data_list_in.push_back(1.0);

std::list<double> data_list_out = transform<std::list>(data_list_in);


How could one (or can one) write this so that the return container can be deduced from the input container?

std::vector<double> data_vector_out = transform(data_vector_in); // No <std::vector>


Although the spirit of this question is similar to The std::transform-like function that returns transformed container, the most popular solution pointed to template metaprogramming which I need to specifically avoid. The solution for using variadic template parameters solved this issue.

Answer Source

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)

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download