Короткая версия моего вопроса такова: как я могу использовать что-то вроде 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";
}
При попытке 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());
Хорошо, возможно, этот вопрос меня не поразил, но, возможно, я получу некоторую поддержку для превращения наших стандартных библиотечных алгоритмов в функциональные объекты и, возможно, добавлю несколько классных подходов к созданию связанных версий указанных алгоритмов. Я думаю, что этот подход намного более разумный (хотя в описанной выше форме, возможно, не настолько полный), чем некоторые из предложениев этой области.