Hvordan `std :: bind ()` en standard bibliotek algoritme?

stemmer
10

Den korte versjonen av mitt spørsmål er dette: Hvordan kan jeg bruke noe sånt std::bind()med en standard bibliotek algoritme?

Siden den korte versjonen er litt blottet for detaljer, her er litt av en forklaring: Anta Jeg har algoritmer std::transform(), og nå ønsker jeg å implementere std::copy()(ja, jeg skjønner at det er std::copy()i standard C ++ bibliotek). Siden jeg er avskyelig lat, jeg tydelig ønsker å bruke den eksisterende implementering av std::transform(). Jeg kunne selvsagt gjøre dette:

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

Somehow denne implementeringen noe føles som en konfigurasjon av en algoritme. For eksempel, virker det som om std::bind()bør være i stand til å gjøre jobben, men bare bruker std::bind()virker ikke:

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

Problemet er at kompilatoren ikke kan finne de aktuelle malparametere fra bare algoritmen, og det spiller ingen rolle om det er en &eller ikke. Er det noe som kan gjøre en tilnærming som å bruke std::bind()arbeid? Siden dette gleder seg, er jeg fornøyd med en løsning å jobbe med noe som allerede er foreslått for inkludering i C ++ standard. Også, for å komme unna med min latskap jeg er glad for å gjøre noe arbeid foran for senere lettere bruk. Tenk på det på denne måten: I min rolle som et bibliotek iverksetter , vil jeg sette ting sammen en gang slik at alle bibliotek brukeren kan være lat: Jeg er en travel iverksetter, men en lat bruker.

I tilfelle du ønsker å ha en ferdig test seng: her er et komplett 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;
}
Publisert på 29/12/2014 klokken 16:25
kilden bruker
På andre språk...                            


1 svar

stemmer
9

Når jeg prøver å std::bind()en overbelastet funksjon kompilatoren kan ikke bestemme hvilke overbelaste å bruke: på det tidspunktet bind()uttrykking evalueres funksjons argumentene er ukjent, dvs. kan overbelastning oppløsningen ikke bestemme hvilke over å plukke. Det er ingen direkte måte i C ++ [ennå?] For å behandle en overbelastning satt som et objekt. Funksjon maler bare generere en overbelastning satt med en overbelastning for hver mulig oppretting. Det vil si at hele problemet med ikke å kunne std::bind()noen av standard C ++ bibliotek algoritmer dreier seg om det faktum at vanlige bibliotek algoritmer er funksjons maler.

En tilnærming for å ha den samme virkning som std::bind()ing en algoritme er å bruke C ++ 14 generiske lambdaene å gjøre bindingen, f.eks:

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

Selv om dette virker det faktisk tilsvarer en fancy implementering av funksjon mal i stedet for å konfigurere en eksisterende funksjon. Men ved hjelp av generiske lambdaene å skape den primære funksjon objekter i en egnet standard bibliotek navne kunne gjøre selve underliggende funksjon gjenstander lett tilgjengelig, for eksempel:

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

Nå, med tilnærming til implementering av transform()det er faktisk trivielt å bruke std::bind()til å bygge copy():

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

Til tross for utseende og bruk av generiske lambdaene er det verdt å påpeke at det faktisk tar omtrent samme innsats for å skape tilsvarende funksjon objekter ved hjelp av kun har tilgjengelig for 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{};

Ja, det er mer å skrive, men det er bare en rimelig liten konstant faktor over bruken av generiske lambdaene, dvs. dersom objekter ved hjelp av generiske lambdaene C ++ 11-versjonen er også.

Selvfølgelig, når vi har funksjonsobjekter for algoritmene kan det være godt å faktisk ikke engang å måtte std::bind()dem som vi hadde behov for å nevne alle de som ikke er bundet argumenter. I eksempelet tilfelle det er currying (vel, jeg tror currying bare gjelder binding det første argumentet, men om det er det første eller det siste argumentet virker litt tilfeldig). Hva om vi hadde curry_first()og curry_last()karri den første eller den siste argumentet? Gjennomføringen av curry_last()er trivielt, også (for kortfattethet Jeg bruker en generisk lambda men det samme omskriving som ovenfor kan brukes til å gjøre den tilgjengelig med 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);
    };
}

Nå, forutsatt at curry_last()bor i samme navne et enten nstd::transformeller identity()definisjonen av copy()kan bli:

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

OK, kanskje dette spørsmålet ikke fikk meg noen lue, men kanskje jeg får noe støtte for å snu våre standard bibliotek algoritmer i funksjonsobjekter og eventuelt legge til noen kule måter å skape innbundne versjoner av nevnte algoritmer. Jeg tror denne tilnærmingen er mye saner (selv i form beskrevet ovenfor muligens ikke like komplett) enn noen av forslaget er i dette området.

Svarte 29/12/2014 kl. 18:13
kilden bruker

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more