Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Functional C++ via template abuse

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.

Addendum 1

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.

like image 616
Wug Avatar asked Oct 16 '12 18:10

Wug


2 Answers

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);
}
like image 175
Dietmar Kühl Avatar answered Sep 21 '22 00:09

Dietmar Kühl


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.

like image 2
David Rodríguez - dribeas Avatar answered Sep 24 '22 00:09

David Rodríguez - dribeas