2014-12-29 9 views
10

Krótka wersja mojego pytania brzmi: jak mogę użyć czegoś takiego jak std::bind() ze standardowym algorytmem bibliotecznym?Jak "std :: bind()" standardowy algorytm biblioteki?

Ponieważ krótka wersja jest nieco pozbawione szczegółów, tutaj jest trochę wyjaśnienie: Załóżmy, że mam algorytmów std::transform() i teraz chcę zaimplementować std::copy() (tak, zdaję sobie sprawę, że istnieje std::copy() w bibliotece standard C++). Ponieważ jestem okropnie leniwy, wyraźnie chcę użyć istniejącej implementacji std::transform(). Mógłbym, oczywiście, to zrobić:

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()); 
} 

Jakoś ta implementacja nieco czuje się jak konfiguracji algorytmu. Na przykład, wydaje się, jakby std::bind() powinien być w stanie wykonać zadanie, ale po prostu za pomocą std::bind() nie działa:

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

Problem polega na tym, że kompilator nie może określić odpowiednie argumenty szablonów jedynie z algorytmem i nie ma znaczenia, czy istnieje &, czy też nie. Czy jest coś, co może zrobić takie podejście, jak przy użyciu pracy std::bind()? Ponieważ jest to oczekiwane, jestem zadowolony z rozwiązania współpracującego z wszystkim, co jest już proponowane do włączenia do standardu C++. Ponadto, aby uciec się od lenistwa, cieszę się, że mogę trochę popracować, aby później łatwiej było z niego korzystać. Pomyśl o tym w ten sposób: w mojej roli jako projektanta biblioteki , będę układał rzeczy raz tak, że każda biblioteka może być leniwy: Jestem pracowitym implementatorem, ale leniwym użytkownikiem.

Jeśli chcesz mieć gotowe łóżko testowe: tutaj jest kompletny program.

#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

@ πάνταῥεῖ: Hej - to prawdziwe pytanie ...! (jasne, podwójne cele odgrywają pewną rolę) –

+0

Połączenie tagów było, co mnie podejrzewało ;-) ... (i to jest oczywiście dobre pytanie) –

+0

Generyczne lambdy to jedyna rzecz, o której mogę pomyśleć, ale Zakładam, że jest niewystarczająco "bind"-like ... –

Odpowiedz

9

Podczas próby std::bind() przeciążenia funkcji kompilator nie może określić, które przeciążenie używać: w momencie bind() -expression ocenia argumenty funkcji są nieznane, czyli rozdzielczość przeciążenie nie może zdecydować, które przeciążenie podnieść. Nie ma bezpośredniej drogi w C++ [jeszcze?], Aby traktować zestaw przeciążeniowy jako obiekt. Szablony funkcji po prostu generują zestaw przeciążeniowy z jednym przeciążeniem dla każdej możliwej instancji. Oznacza to, że cały problem polegający na tym, że nie można uzyskać żadnego z standardowych algorytmów biblioteki C++, dotyczy faktu, że standardowe algorytmy biblioteczne są szablonami funkcyjnymi.

Jednym ze sposobów, aby mieć taki sam skutek jak std::bind() ing algorytm jest użycie C++ 14 lambdas Generic zrobić wiązania, np:

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

Chociaż to działa w rzeczywistości jest to równoważne z wymyślna implementacja szablonu funkcji zamiast konfigurowania istniejącej funkcji. Jednak użycie ogólnych lambd do utworzenia podstawowych obiektów funkcji w odpowiedniej przestrzeni nazw standardowej biblioteki może sprawić, że rzeczywiste podstawowe obiekty funkcji będą łatwo dostępne, np.:

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

Teraz, z podejściem do wdrażania transform() to jest rzeczywiście banalny w obsłudze std::bind() zbudować copy():

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

Pomimo wyglądu i wykorzystania lambdas generycznych Warto podkreślić, że rzeczywiście z mniej więcej takim samym wysiłkiem tworzy się odpowiednie obiekty funkcji, wykorzystując tylko funkcje dostępne dla 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{}; 

Tak, jest to bardziej typowe, ale jest to rozsądny mały stały czynnik w porównaniu z używaniem ogólnych znaków lambda, to znaczy, jeśli obiekty używające generycznych lambd, to jest też wersja C++ 11.

Oczywiście, gdy już mamy obiekty funkcyjne dla algorytmów, może nie być tak, że nawet nie trzeba ich mieć pod std::bind(), ponieważ musimy wspomnieć o wszystkich nie związanych argumentach. W przykładowym przypadku jest to currying (cóż, myślę, że currying dotyczy tylko wiązania pierwszego argumentu, ale to, czy jest to pierwszy, czy ostatni argument, wydaje się nieco losowy). Co by było, gdybyśmy mieli curry_first() i curry_last() na curry pierwszego lub ostatniego argumentu? Realizacja curry_last() jest trywialne, zbyt (dla uproszczenia używam rodzajowe lambda ale to samo przepisanie jak powyżej mogą być wykorzystane do udostępnienia z 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); 
    }; 
} 

Teraz, zakładając, że curry_last() mieszka w tym samym przestrzeń nazw albo nstd::transform lub identity() definicja copy() może stać:

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

OK, może to pytanie nie dostał mi żadnych kapelusz, ale być może będę miał pewne wsparcie dla toczenia nasze standardowe algorytmy biblioteki do obiektów funkcji i ewentualnie dodanie kilku fajnych pproaches do tworzenia powiązanych wersji wspomnianych algorytmów. Myślę, że to podejście jest znacznie bardziej uciążliwe (chociaż w formie opisanej powyżej prawdopodobnie nie jest kompletna) niż niektóre z proposal s w tym obszarze.

+0

Istnienie niestandardowych rozszerzeń (nawet dobrych) nie oznacza, że ​​jest to wymagane w C++! (Konstruktor 'constexpr' tworzenia/przenoszenia/kopiowania na bezstanowej lambda nie jest wymagany przez standard.Nie jest wykluczony, jeśli dobrze pamiętam) – Yakk

+0

@Yakk: tak, zostałem oszukany przez rozszerzenie (faktycznie tylko w gcc nie w klang). –

+0

@Yakk lambdas może nie pojawiać się w stałych wyrażeniach, myślę, że wyklucza "jest dozwolony, ale nie przenośny" – dyp

Powiązane problemy