Mapping a list to a different type

Viewed 89

I'm trying to make a List class, mainly because there are some methods that aren't present for vectors that I'd like to implement myself. One of these is a map() function where you can pass in a lambda as a parameter and the method returns a new List in which each of the values in the old list was passed to the function.

The problem I'm having is with getting the method to deal with returning a List featuring a different type. For example:

List<Option<int>> y;
y.append(option(1));
y.append(option(2));
y.append(option(3)); 
//y is [Option(1), Option(2), Option(3)]

List<int> z = y.map([](Option<int> o) {return o.get();});

//z should be [1,2,3]

Where the map() function, in it's current state, is defined as:

template<typename S>
List<S> map(S (* function)(T)) {
    List<S> output;     
    for (int i = 0; i < size(); i++) {
        output.append(function((*this)[i]));
    }
    return output;
}

The problem is that the above doesn't seem to allow me to pass in a lambda, only a previously defined function, which defeats the purpose of convenience for the method. The only way I can get it to accept a lambda is by defining the function as:

template<typename S, typename R>
List<S> map(R function) {
    List<S> output;  
    for (int i = 0; i < size(); i++) {
        output.append(function((*this)[i]));
    }
    return output;
}

But this results in a different problem, where it says:

no instance of function template 'List<T>::map[with T=Option<int>]' matches the argument list

Is there a particular workaround for this, or will I have to declare functions for each time I call the map() function?

1 Answers

Use decltype. In your definition, you want to determine the type of the output list return value with what would be returned from the function. So you can do something like this:

template<typename R>
auto map(R function) {
    List<std::decay_t<decltype(function(operator[](0)))>> output;
    for(int i = 0; i < size(); i++) output.append(function(operator[](i)));
    return output;
}

The type of the list elements is whatever function returns when called on the elements of this. The std::decay_t turns types you can't store (references, cv-qualified types, arrays, etc) into those you can (resp. non-references, none-qualified types, pointers, etc.).

A slightly more powerful result can be achieved with std::invoke_result, which lets you replace the function call with std::invoke:

template<typename T>
struct List {          // Your list class, we need the T
    template<typename R>
    auto map(R function) {
        List<std::decay_t<std::invoke_result_t<R, T&>>> output; // assuming that accesses give references
        for(int i = 0; i < size(); i++) output.append(std::invoke(function, operator[](i)));
        return output;
    }
};

The result is that now you can also map things like pointers to members over your list.

Godbolt

Related