Image

Listens: Lacuna Coil — [Dark Adrenaline (Japanese Edition) #04] Give Me Something More

Как написать функцию map на C++

Задача: написать такую функцию map, которой можно было бы скормить вектор и функтор или указатель на функцию, которая принимает либо один аргумент (элемент вектора), либо два (еще и его индекс), может что-нибудь возвращать или нет (тогда map должен возвращать вектор возвращаемых типов, либо void).

(Де)мотивирующий пример:

void out(int x) { cout << x << endl; }
void outidx(int x, int idx) { cout << "[" << idx << "] = " << x << endl; }
double inch(int x) { return x * 2.54; }

int main()
{
    using ultra::map;
    vector<int> a; a.push_back(2); a.push_back(3); a.push_back(4);
    map(a, [] (int x) { out(x); });
    map(a, [] (int x, int idx) { outidx(x, idx); });
    map(a, out);
    map(a, outidx);
    vector<double> r1 = map(a, inch);
    vector<double> r2 = map(a, [] (int x) { return inch(x); });
    vector<double> r3 = map(a, inch);
    auto res = map(a, [&a] (int x, int idx) -> double { return a[2] * x + idx; });
}

По поводу реализации такого ultra::map:
Imageudpn: у тебя нет в запасе какого-нибудь способа развидеть?


Такой map должен сразу понимать, что ему передали, и в зависимости от этого что-то делать. Воспользуемся механизмом перегрузки функций, чтобы отделить указатели на функции от функторов. Функторы будут попадать в наиболее общую функцию

template<typename A, typename U>
auto map(const vector<A>& v, U f)
    -> decltype(map_h(v, f, &U::operator()))
{
    return map_h(v, f, &U::operator());
}

которая просто будет разруливать подобно указателям на функции уже указатели на функции-члены (и заодно передадим в разруливатель сам функтор, чтобы было от кого потом вызывать функцию-член).

Посчитаем, сколько понадобится рассмотреть частных случаев. Функция может принимать 1 или 2 аргумента, возвращать void или не void. Вдобавок функция-член может быть константной или не константной. Итого 22 + 23 = 12. Как-то лень писать их все ручками, давайте напишем макрогенератор, который в зависимости от нескольких битовых аргументов будет вставлять разные куски кода в функцию. Увы, #if внутрь #define засунуть не получится, так что придется использовать BOOST_PP_IF. Еще неприятным моментом является то, что нельзя в BOOST_PP_IF использовать запятую (иначе препроцессор решит, что это разделитель аргументов), в том числе и если она получается после макроподстановок. Поэтому нужно использовать примерно такой трюк:

#define BOOST_PP_COMMA_IF(cond) \
    BOOST_PP_IF(cond, BOOST_PP_COMMA, BOOST_PP_EMPTY)()

А вот и сам код:

namespace ultra {

#define GEN(functor, twoarg, isvoid, isconst) \
    template<typename Tin, \
        BOOST_PP_IF(functor, typename TFunctor BOOST_PP_COMMA, BOOST_PP_EMPTY)() \
        BOOST_PP_IF(functor, typename TClass BOOST_PP_COMMA, BOOST_PP_EMPTY)() \
        BOOST_PP_IF(isvoid, BOOST_PP_EMPTY, typename Tout BOOST_PP_COMMA)() \
        typename T1 \
        BOOST_PP_COMMA_IF(twoarg) \
        BOOST_PP_IF(twoarg, typename T2, ) \
    > \
    BOOST_PP_IF(isvoid, void, vector<Tout>) BOOST_PP_IF(functor, map_h, map) ( \
        const vector<Tin>& v, \
        BOOST_PP_IF(functor, TFunctor fun BOOST_PP_COMMA, BOOST_PP_EMPTY)() \
        BOOST_PP_IF(isvoid, void, Tout) ( BOOST_PP_IF(functor, TClass::*f, *f) ) \
            ( T1 BOOST_PP_COMMA_IF(twoarg) BOOST_PP_IF(twoarg, T2, ) ) \
            BOOST_PP_IF(isconst, const, ) \
    ) { \
        BOOST_PP_IF(isvoid, , vector<Tout> result(v.size());) \
        for (size_t i = 0, ilen = v.size(); i < ilen; ++i) { \
            BOOST_PP_IF(isvoid, , result[i] = ) \
                ( BOOST_PP_IF(functor, fun .*, ) f) \
                ( v[i] BOOST_PP_COMMA_IF(twoarg) BOOST_PP_IF(twoarg, i, ) ) ; \
        } \
        BOOST_PP_IF(isvoid, , return result;) \
    }


GEN(1,0,0,0)
GEN(1,0,0,1)
GEN(1,0,1,0)
GEN(1,0,1,1)
GEN(1,1,0,0)
GEN(1,1,0,1)
GEN(1,1,1,0)
GEN(1,1,1,1)

template<typename A, typename U>
auto map(const vector<A>& v, U f)
    -> decltype(map_h(v, f, &U::operator()))
{
    return map_h(v, f, &U::operator());
}

GEN(0,0,0,0)
GEN(0,0,1,0)
GEN(0,1,0,0)
GEN(0,1,1,0)
#undef GEN

}


Удачного метапрограммирования! :)