Можно ли сделать аргументы типа выводимыми для шаблонов функций, использующих контейнер std? - PullRequest
0 голосов
/ 01 апреля 2020

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

github ссылка

Часть кода:

template <typename T, typename U>
U foldLeft(const std::vector<T>& data,
           const U& initialValue,
           const std::function<U(U,T)>& foldFn) {
    typedef typename std::vector<T>::const_iterator Iterator;
    U accumulator = initialValue;
    Iterator end = data.cend();
    for (Iterator it = data.cbegin(); it != end; ++it) {
        accumulator = foldFn(accumulator, *it);
    }
    return accumulator;
}

template <typename T, typename U>
std::vector<U> map(const std::vector<T>& data, const std::function<U(T)> mapper) {
    std::vector<U> result;
    foldLeft<T, std::vector<U>&>(data, result, [mapper] (std::vector<U>& res, T value)  -> std::vector<U>& {
        res.push_back(mapper(value));
        return res;
    });
    return result;
}

Пример использования:

std::vector<int> biggerInts = map<int,int>(test, [] (int num) { return num + 10; });

Аргументы типа T, U должны быть полностью квалифицированы для этого скомпилировать, как показано в примере, например, map (...). Эта реализация предназначена для C ++ 11, как упомянуто на связанной странице.

Возможно ли теперь с более новыми версиями C ++ (или даже 11) использовать это менее многословно, то есть делать типы U, T выводить автоматически? Я гуглил по этому поводу и обнаружил, что в шаблоне class , по-видимому, есть некоторые улучшения, в отличие от шаблона функции, выведения аргументов в C ++ 17. Но поскольку я когда-либо использовал шаблоны только в довольно простой манере c, мне было интересно, существует ли что-то существующее, о чем я не знаю, которое могло бы улучшить эту реализацию в плане подробности.

Ответы [ 2 ]

3 голосов
/ 01 апреля 2020

Вы можете переписать map подпись так:

template <typename T, typename M, typename U = decltype(std::declval<M>()(T{}))>
std::vector<U> map(const std::vector<T>& data, const M mapper)

, тогда T будет выведено как value_type элементов вектора.

M - любой вызываемый объект.

U выводится как тип возврата функтора M() при вызове для T{}.

Ниже

std::vector<int> biggerInts = map(test, [] (int num) { return num + 10; });
                              ^^^^ empty template arguments list

отлично работает.

Демонстрационная версия

1 голос
/ 01 апреля 2020

Более общие шаблоны упрощают вывод аргументов шаблона.

Один принцип: часто ошибочно использовать std::function в качестве параметра шаблонной функции. std::function - это стирание типа, предназначенное для использования, когда что-то должно хранить какую-то неизвестную вызываемую вещь как определенный c тип. Но шаблоны уже способны обрабатывать любой произвольный вызываемый тип. Поэтому, если мы просто используем шаблонный шаблон generi c typename FuncT, он может быть выведен для необработанного указателя на функцию, лямбда-выражения или другого класса с непосредственным operator().

также сделайте более общий вывод и примите любой входной контейнер вместо vector, а затем определите по нему T, если он вообще необходим.

Так что для C ++ 11 я бы переписал это:

// C++20 is adding std::remove_cvref, but it's trivial to implement:
template <typename T>
using remove_cvref_t =
    typename std::remove_cv<typename std::remove_reference<T>::type>::type;

template <typename Container, typename U, typename FuncT>
remove_cvref_t<U> foldLeft(
           const Container& data,
           U&& initialValue,
           const FuncT& foldFn) {
    remove_cvref_t<U> accumulator = std::forward<U>(initialValue);
    for (const auto& elem : data) {
        accumulator = foldFn(std::move(accumulator), elem);
    }
    return accumulator;
}

template <typename Container, typename FuncT>
auto map(const Container& data, const FuncT& mapper)
    -> std::vector<remove_cvref_t<decltype(mapper(*std::begin(data)))>>
{
    using T = remove_cvref_t<decltype(*std::begin(data))>;
    using ResultT = std::vector<remove_cvref_t<decltype(mapper(std::declval<const T&>()))>>;
    ResultT result;
    foldLeft(data, std::ref(result), [&mapper] (ResultT &res, const T& value)  -> ResultT& {
        res.push_back(mapper(value));
        return res;
    });
    return result;
}

См. Рабочую программу для coliru .

В старом map была одна неприятная вещь: он потенциально копировал вектор результата на каждой итерации. = в accumulator = foldFn(accumulator, *it); является самостоятельным назначением, которое может ничего не делать или может выделять новую память, копировать содержимое, затем освобождать старую память и обновлять контейнер. Поэтому вместо этого я изменил U для foldLeft в этом случае на std::reference_wrapper. = в этом случае все равно будет «перепривязывать» оболочку к тому же объекту, но это, по крайней мере, будет быстрым.

В C ++ 14 и более поздних версиях вы могли бы покончить с поиском T в пределах map с использованием обобщенной c лямбды: [&mapper] (std::vector<U>& res, const auto& value) ...

...