Как `std :: bind ()` алгоритм стандартной библиотеки?

Короткая версия моего вопроса такова: как я могу использовать что-то вроде std::bind() со стандартным алгоритмом библиотеки?

Так как короткая версия немного лишена деталей, вот небольшое объяснение: предположим, у меня есть алгоритмы std::transform() и теперь я хочу реализовать std::copy() (да, я понимаю, что есть std::copy() в стандартной библиотеке C ++). Так как я ужасно ленив, я явно хочу использовать существующую реализацию std::transform(), Я мог бы, конечно, сделать это:

struct identity {
template <typename T>
auto operator()(T&& value) const -> T&& { return std::forward<T>(value); }
};
template <typename InIt, typename OutIt>
auto copy(InIt begin, InIt end, OutIt to) -> OutIt {
return std::transform(begin, end, to, identity());
}

Каким-то образом эта реализация выглядит как конфигурация алгоритма. Например, кажется, будто std::bind() должен быть в состоянии сделать работу, но просто используя std::bind() не работает:

namespace P = std::placeholders;
auto copy = std::bind(std::transform, P::_1, P::_2, P::_3, identity());

Проблема в том, что компилятор не может определить соответствующие аргументы шаблона только из алгоритма, и не имеет значения, есть ли & или нет. Есть ли что-то, что может сделать подход, как использование std::bind() Работа? Поскольку это с нетерпением, я доволен решением, работающим со всем, что уже предложено для включения в стандарт C ++. Кроме того, чтобы избавиться от своей лени, я рад сделать некоторую работу заранее для последующего более легкого использования. Думайте об этом так: в моей роли библиотеки исполнитель, Я соберу вещи однажды так, чтобы каждая библиотека пользователь может быть ленивым: я занятый разработчик, но ленивый пользователь.

Если вы хотите иметь готовый испытательный стенд: вот полная программа.

#include <algorithm>
#include <functional>
#include <iostream>
#include <iterator>
#include <utility>
#include <vector>

using namespace std::placeholders;

struct identity {
template <typename T>
T&& operator()(T&& value) const { return std::forward<T>(value); }
};int main()
{
std::vector<int> source{ 0, 1, 2, 3, 4, 5, 6 };
std::vector<int> target;

#ifdef WORKS
std::transform(source.begin(), source.end(), std::back_inserter(target),
identity());
#else
// the next line doesn't work and needs to be replaced by some magic
auto copy = std::bind(&std::transform, _1, _2, _3, identity());
copy(source.begin(), source.end(), std::back_inserter(target));
#endif
std::copy(target.begin(), target.end(), std::ostream_iterator<int>(std::cout, " "));
std::cout << "\n";
}

11

Решение

При попытке std::bind() перегруженная функция компилятор не может определить, какую перегрузку использовать: во время bind()-выражение оценивается, аргументы функции неизвестны, т.е. разрешение перегрузки не может решить, какую перегрузку выбрать. В C ++ [пока?] Нет прямого способа трактовать набор перегрузки как объект. Шаблоны функций просто генерируют набор перегрузки с одной перегрузкой для каждого возможного экземпляра. То есть вся проблема не в том, чтобы std::bind() Любой из стандартных библиотечных алгоритмов C ++ вращается вокруг того факта, что стандартные библиотечные алгоритмы являются шаблонами функций.

Один подход, чтобы иметь тот же эффект, что и std::bind()алгоритм должен использовать C ++ 14 общие лямбды сделать привязку, например:

auto copy = [](auto&&... args){
return std::transform(std::forward<decltype(args)>(args)..., identity());
};

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

namespace nstd {
auto const transform = [](auto&&... args){
return std::transform(std::forward<decltype(args)>(args...));
};
}

Теперь с подходом к реализации transform() это на самом деле тривиально использовать std::bind() строить copy():

auto copy = std::bind(nstd::transform, P::_1, P::_2, P::_3, identity());

Несмотря на внешний вид и использование универсальных лямбд, стоит отметить, что для создания соответствующих функциональных объектов на самом деле требуется примерно столько же, используя только функции, доступные для C ++ 11:

struct transform_t {
template <typename... Args>
auto operator()(Args&&... args) const
-> decltype(std::transform(std::forward<decltype(args)>(args)...)) {
return std::transform(std::forward<decltype(args)>(args)...);
}
};
constexpr transform_t transform{};

Да, это больше типизирование, но это лишь небольшой разумный постоянный фактор по сравнению с использованием универсальных лямбд, т. Е. Если объекты, использующие универсальные лямбды, тоже версия C ++ 11.

Конечно, как только у нас есть функциональные объекты для алгоритмов, это может быть очень удобно, даже если std::bind() их, как мы должны были бы упомянуть все не связанные аргументы. В этом примере это Выделка (ну, я думаю, что каррирование применимо только к привязке первого аргумента, но будет ли это случайным первый или последний аргумент). Что если бы у нас было curry_first() а также curry_last() карри первый или последний аргумент? Реализация curry_last() тоже тривиально (для краткости я использую универсальную лямбду, но то же самое, что и выше, можно использовать, чтобы сделать ее доступной в C ++ 11):

template <typename Fun, typename Bound>
auto curry_last(Fun&& fun, Bound&& bound) {
return [fun = std::forward<Fun>(fun),
bound = std::forward<Bound>(bound)](auto&&... args){
return fun(std::forward<decltype(args)>(args)..., bound);
};
}

Теперь, предполагая, что curry_last() живет в том же пространстве имен либо nstd::transform или же identity() определение copy() мог стать:

auto const copy = curry_last(nstd::transform, identity());

Хорошо, возможно, этот вопрос меня не поразил, но, возможно, я получу некоторую поддержку для превращения наших стандартных библиотечных алгоритмов в функциональные объекты и, возможно, добавлю несколько классных подходов к созданию связанных версий указанных алгоритмов. Я думаю, что этот подход намного более разумный (хотя в описанной выше форме, возможно, не настолько полный), чем некоторые из предложениев этой области.

10

Другие решения


По вопросам рекламы [email protected]