Search code examples
c++c++11templateslambdatemplate-argument-deduction

In C++11 is there a way to not require template arguments when calling a function that takes any callable as an argument, including bound methods?


In order to broaden my understanding of C++11, I'm experimenting with writing functional helpers and seeing if I can make calling them less verbose. Consider the following code:

#include <list>

int timesTwo(int x) { return x*2; }
int timesX(int x, int y) { return x*y; }

class Foo {
public:
    Foo(int a) : value(a) {};
    int fooTimesTwo() const { return value*2; };
    int value;
};

template <class T, class U>
std::list<U> myMap(const std::list<T> &list, const std::function<U(const T &)> &func)
{
    std::list<U> result;

    for(typename std::list<T>::const_iterator it = list.begin(); it != list.end(); ++it) {
        result.push_back(func(*it));
    }

    return result;
}

int main()
{
    std::list<int> numbers = {1,2,3,4,5};
    std::list<int> numbers2 = myMap<int,int>(numbers, [] (int x) { return x*2; });
    std::list<int> numbers3 = myMap<int,int>(numbers, &timesTwo);
    std::list<int> numbers4 = myMap<int,int>(numbers, std::bind(timesX, 2, std::placeholders::_1));

    std::list<Foo> fooList = {Foo(1), Foo(2), Foo(3), Foo(4)};
    std::list<int> numbers5 = myMap<Foo,int>(fooList, &Foo::fooTimesTwo);    
    return 0;
}

Is there anyway to rewrite myMap so that

  1. all four of the calls to it in my code example do not require any template arguments, and...

  2. there's only one general implementation, and I don't have to manually write an overloaded version for each combination of types I want to call it with?

I've tried changing the second argument of myMap to be a third templated type instead of std::function, but it fails because a) the second templated type U can't be inferred, and b) even if it could, the fourth call to myMap will cause an error on line 20 due to &Foo::fooTimesTwo not being a function or function pointer.

I'm willing to consider all of the various features of C++11 to do this, and I don't particularly care if it makes the declaration or definition of myMap obtuse or unreadable. I'm just wondering if it's possible and, if so, what sort of techniques and C++11 features can accomplish it.


Solution

  • You could attempt to implement std::invoke in C++11, but I think it would be really cumbersome.

    It's fairly simple to make a function template for a generalized callable:

    template <class T, class F>
    auto myMap(const std::list<T> &list, F&& func)
        -> std::list<typename std::decay<decltype(func(*list.begin()))>::type>
    {
        using U = typename std::decay<decltype(func(*list.begin()))>::type;
        std::list<U> result;
    
        for(typename std::list<T>::const_iterator it = list.begin(); it != list.end(); ++it) {
            result.push_back(func(*it));
        }
    
        return result;
    }
    

    You get expression sfinae for free. Now there's only member function pointer to be taken care of:

    template <class T, class F>
    auto myMap(const std::list<T> &list, F&& func)
        -> std::list<typename std::decay<decltype(((*list.begin()).*func)())>::type>
    {
        return myMap(list, [=](T const& t){ return (t.*func)(); });
    }
    

    Now you can call your functions as expected. demo


    While at it, you could replace this ugly for loop with a ranged for:

    for(auto const& elem : list) {
        results.push_back(func(elem));
    }
    

    Or use an algorithm:

    std::transform(list.begin(), list.end(), std::back_inserter(result), func);