I decided to try to write a functional map implementation in C++ using templates, and this is what I've come up with:
template <
class U,
class V,
template <class> class T
>
class T<V> WugMap(
class T<U>::const_iterator first,
class T<U>::const_iterator second,
V (U::*method)() const)
{
class T<V> collection;
while (first != second)
{
collection.insert(collection.end(), ((*(first++)).*method)());
}
return collection;
}
Now this is all fine and dandy, and even compiles. Problem is, I have no idea how to actually call it.
Trying the naive way yields the following error:
prog.cpp:42: error: no matching function for call to
‘WugMap(__gnu_cxx::__normal_iterator<Container*, std::vector<Container,
std::allocator<Container> > >, __gnu_cxx::__normal_iterator<Container*,
std::vector<Container, std::allocator<Container> > >, int (Container::*)()const)’
As far as I can tell, all of the arguments are correct. gcc isn't suggesting any alternatives at all, which leads me to believe that my definition of WugMap is suspect, but it compiles fine, so I'm rather lost. Can anyone guide me through this silliness?
If anyone can suggest a better way to write a function like this that will support consuming any type of collection containing any type of object, I'll look into changing it.
Here's my ideone so far.
I'm currently using Ideone, which is using C++03, gcc 4.3.4.
Is this possible in C++11? It's been hinted that it is. I know templates in C++11 support varying numbers of arguments so I'll modify my requirements to suit that as well. I'll put a bit of effort into writing something up, but in the meantime, here are the requirements that I'm looking for:
Should have a signature something like the following:
C2<V, ...> map(const C1<U, ...>&, V (U::*)(...), ...)
That's taking some collection C1, containing elements of type U, constructed with some number of default parameters, by reference, and also taking some member function (returning V and taking some number of arguments of unknown types) of U, and then taking, in order, arguments to be passed to the member function. The function will finally return a collection of type C2 containing elements of type V and being initialized with an unknown number of default parameters.
Should be chainable:
vector<int> herp = map(
map(
set<Class1, myComparator>(),
&Class1::getClass2, 2, 3),
&Class2::getFoo);
Bonus points if I don't have to have template arguments or any other extra verbosity when using it.
std::transform
is great, but not chainable.
Template arguments can never be deduced from nested types. Even if U
and V
can be deduced from the member function pointer, you won't be able to deduce the template type T
.
Explicitly specifying the template arguments as in the link to ideone (I didn't the the link before writing the statement above) doesn't work either, mainly because the template arguments for std::vector
are not just a single type T
! std::vector
takes a value type and an allocator type. Fixing things up is getting fairly ugly:
#include <vector>
#include <iostream>
using namespace std;
class Container
{
public:
Container() {}
Container(int _i) : i(_i) {}
int get_i() const {return i;}
int i;
};
template <
class U,
class V,
template <typename...> class T
>
T<V> WugMap(
typename T<U>::const_iterator first,
typename T<U>::const_iterator second,
V (U::*method)() const)
{
T<V> collection;
while (first != second)
{
collection.insert(collection.end(), ((*(first++)).*method)());
}
return collection;
}
int main()
{
vector<Container> containers;
for (int i = 0; i < 10; ++i) containers.push_back((Container(i)));
WugMap<Container, int, std::vector>(
containers.begin(), containers.end(), &Container::get_i);
}
Not really sure whether this should be an answer, but heck:
std::vector<std::string> src = f();
std::vector<std::string::size_type> sizes;
sizes.reserve(src.size());
// Actual transformation:
std::transform( src.begin(), src.end(), std::back_inserter(sizes),
[](std::string const& s) { return s.size(); } );
Similar things can be done manually, but there is really no point in reinventing the reinvented wheel.
As to what is different in the std::transform
case, it does not try to bind types so tightly, it takes Iter1
for the first two arguments, Iter2
for the third argument and Functor
for the third. There are no checks on the interface to guarantee that Iter1
and Iter2
are iterators into the same type of container, or that Functor
will transform from the value type in the first container to the value type in the second.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With