Ответ 1
При попытке std::bind()
перегруженной функции компилятор не может определить, какую перегрузку использовать: во время вычисления выражения bind()
аргументы функции неизвестны, т.е. разрешение перегрузки не может решить, какая перегрузка подобрать. В С++ [еще?] Нет прямого пути для обработки набора перегрузки как объекта. Шаблоны функций просто генерируют набор перегрузки с одной перегрузкой для каждого возможного экземпляра. То есть вся проблема неспособности std::bind()
любого из стандартных алгоритмов библиотеки С++ вращаться вокруг того факта, что стандартные библиотечные алгоритмы являются функциональными шаблонами.
Один из подходов к тому, чтобы иметь такой же эффект, как и std::bind()
с использованием алгоритма, - использовать С++ 14 generic lambdas для выполнения привязки, например:
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());
Несмотря на внешний вид и использование общих лямбда, стоит отметить, что на самом деле требуется примерно то же самое усилие для создания соответствующих объектов функций с использованием только функций, доступных для С++ 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{};
Да, это больше типизация, но это всего лишь разумный небольшой постоянный фактор при использовании общих лямбда, т.е. если объекты, использующие общие lambdas, также имеют версию С++ 11.
Конечно, как только у нас есть функциональные объекты для алгоритмов, он может быть аккуратным, даже не имея при этом std::bind()
их, поскольку нам нужно будет упомянуть все не связанные аргументы. В примере это currying (ну, я думаю, что currying применим только к привязке первого аргумента, но является ли он первым или последним аргументом немного случайным), Что делать, если мы имели curry_first()
и curry_last()
для того, чтобы выполнить первый или последний аргумент? Реализация curry_last()
тоже тривиальна (для краткости я использую общую лямбда, но тот же самый переписывающий, что и выше, можно использовать, чтобы сделать его доступным с С++ 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());
ОК, может быть, этот вопрос не вызвал у меня никакой шляпы, но, может быть, я получу некоторую поддержку для превращения наших стандартных библиотечных алгоритмов в объекты функций и, возможно, добавления нескольких интересных подходов к созданию связанных версий указанных алгоритмов. Я думаю, что этот подход намного более здравый (хотя в описанной выше форме, возможно, не такой полный), чем какой-либо из предложений в этой области.