2014-12-29 3 views
10

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

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

@ πάνταῥεῖ: Эй, это неподдельный вопрос ...! (конечно, двойные цели действительно играют определенную роль) –

+0

Комбинация тегов была, что заставило меня подозревать ;-) ... (и это, конечно, хороший вопрос) –

+0

Общий лямбда - это почти единственное, что я могу придумать, но Я предполагаю, что недостаточно 'bind'-like ... –

ответ

9

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

Один подход, чтобы иметь такой же эффект, как std::bind() ИНГ алгоритм заключается в использовании C++ за 14 Generic Лямбдами сделать обязательными, например:

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{}; 

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

Конечно, если у нас есть функциональные объекты для алгоритмов, то он может быть аккуратным, даже если у него не должно быть std::bind(), так как нам нужно будет упомянуть все не связанные аргументы. В примере это currying (ну, я думаю, что currying применим только к привязке первого аргумента, но является ли первый или последний аргумент немного случайным). Что, если бы у нас были 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()); 

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

+0

Существование нестандартных расширений (даже хороших) не означает, что это необходимо в C++! (конструктор создания/перемещения/копирования 'constexpr' на ассемблерной лямбда не требуется стандартом. Ничто не исключается, если я правильно помню) – Yakk

+0

@Yakk: да, меня обманули расширение (фактически только в gcc не в clang). –

+0

@ Yakk lambdas может не отображаться в постоянных выражениях, я думаю, что это исключает «разрешено, но не переносимо». – dyp

Смежные вопросы