2012-03-30 18 views
7

Próbuję zrozumieć pojęcie curry i wywołanie funkcji, która konkluduje trzy ciągi, ale przechodząc tylko dwa ciągi i używając drugiego argumentu dwa razy.C++ Funkcja wiążą powtarzające się argumenty dla funkcji curry

Jednak gdy to zrobię, drugi argument nie zostanie w ogóle wysłany do funkcji i wydrukuje pusty ciąg znaków. Czy to naprawdę oczywisty błąd?

string concatthreestrings(string a,string b,string c){ 
    cout<<"Value of A: "<<a<<endl; 
    cout<<"Value of B: "<<b<<endl; 
    cout<<"Value of C: "<<c<<endl; 
    return a+b+c; 
} 


int main() 
{ 
    typedef std::function< string(string,string) > fun_t ; 
    using namespace std::placeholders; 
    fun_t fn = std::bind(concatthreestrings, _1, _2, _2); 
    cout<<endl<<fn("First","Second")<<endl; 

} 

Podaje poniżej dane wyjściowe. Nie używanie _2 dwa razy oznacza, że ​​drugi argument zostanie przekazany zarówno dla drugiego, jak i trzeciego. Jeśli użyjesz napisu na swoim miejscu, działa dobrze.

enter image description here

+0

Bardzo interesujące pytanie, nie spodziewałbym się takiego zachowania! –

+1

Po prostu dodać korektę: To nie jest naprawdę przygnębiające. Wiążące argumenty i curry to dwie bardzo podobne, ale wciąż odrębne operacje, których nie należy mylić. Currying oznacza przyjęcie funkcji, która przyjmuje funkcję N argumentów i przekształca ją w funkcję jednego argumentu, który zwraca funkcję jednego argumentu, który zwraca funkcję jednego argumentu ... (powtórzone n razy). Możesz użyć 'std :: bind' do zaimplementowania funkcji' curry', która robi to za Ciebie (w pewnym zakresie). Podobnie możesz użyć currying do implementacji wiązania argumentów w sposób 'std :: bind'. – LiKao

+1

@LiKao: Rzeczywiście, "bind" pozwala [częściowe zastosowanie] (http://en.wikipedia.org/wiki/Partial_application), a nie curry. –

Odpowiedz

5

ciągi Kopiowanie jest drogie. Ponieważ std::bind uważa, że ​​wartości symboli zastępczych są używane tylko raz, wykonuje on ciąg znaków w postaci std::move. Odbywa się to dla każdego parametru iw konsekwencji, albo b lub c jest przeniesiony, co oznacza pusty ciąg.

Możesz zmienić to zachowanie, wyraźnie mówi, co masz na myśli, przekazując argumenty przez const odniesień:

string concatthreestrings(string const& a,string const& b,string const& c) 

Teraz powinno działać.

+2

Czy jesteś pewien, że to zachowanie jest dobrze zdefiniowane? Z tego co przeczytałem w standardzie, opakowanie zwracane przez 'bind' przekazuje swoje argumenty do funkcji opakowanej. W tym przypadku, zgodnie z moim rozumowaniem, będziemy mieli opakowanie w ten sposób (po dedukowaniu parametrów szablonu i zwijaniu): 'string g (const char (& u1) [6], const char (& u2) [7]) { concatthrerings (forward (u1), forward (u2), forward (u2)); } '. Trzy ciągi "a", "b" i "c" zostałyby zbudowane z tablic, więc nie widzę miejsca, w którym nastąpiłby ruch. –

+0

@ipc: Czy jest jakiś sposób, w którym mogę określić to zachowanie w std :: bind zamiast zmieniać samą definicję funkcji – ganeshran

+0

Uzyskiwanie dostępu do przeniesionego obiektu to UB. –

2

Zrobiłem kilka testów przy użyciu tego mniejszego przykład, który wykazuje takie samo zachowanie Masz:

#include <functional> 
#include <iostream> 
#include <string> 

using std::string; 

void print(string s1, string s2) 
{ 
    std::cout << s1 << s2 << '\n'; 
} 

int main() 
{ 
    using namespace std::placeholders; 

    typedef std::function< void(string) > fn_t; 

    fn_t func = std::bind(print, _1, _1); 

    std::string foo("foo"); 
    func(foo); 
} 

// outputs: foo 

pamiętać, że określony obiekt String o nazwie „foo” zamiast używać literały ciągów. Zachowanie jest takie samo, więc problem nie jest z tym związany.

Myślę, że problem pochodzi z twojego typedef. Zwrot z bind (który jest nieokreślony) jest rzutowany na funkcję przyjmującą wartość stringwedług wartości, podczas gdy opakowanie zwrócone przez bind prawdopodobnie bierze swoje argumenty za pomocą rvalue-reference i idealnie przekazuje je dalej. Zamiast używać własnego słowa kluczowego, powinieneś użyć słowa kluczowego auto, aby typ func został automatycznie wyprowadzony przez kompilator. Jeśli będziemy modyfikować główny następująco otrzymujemy oczekiwane zachowanie:

int main() 
{ 
    using namespace std::placeholders; 

    auto func = std::bind(print, _1, _1); 

    std::string foo("foo"); 
    func(foo); 
} 

// outputs: foofoo 

Innym rozwiązaniem jest wymienić typedef tak że func bierze swoją parametr przez odniesienie do const:

typedef std::function< void(string const &) > fn_t; 

I don Naprawdę rozumiem, dlaczego drugi typedef nie działa ... Przypuszczalnie ciąg jest przenoszony, jak zauważyła @ipc, ale nie wiem, w którym momencie wykonania tak się dzieje. Nie jestem nawet pewien, czy jest to standardowe zachowanie, ponieważ zarówno function, jak i opakowanie zwrócone przez bind powinny używać idealnego przekazywania. Być może GCC zawiera pewne optymalizacje, które przesuwają argumenty opakowania, gdy są przekazywane przez wartość?

Edit

Zrobiłem kilka testów, okazuje się wdrożenie GCC z std::function wykonuje ruch na swoich argumentów, natomiast powrót wrapper przez std::bind nie. Nadal nie wiem, czy to jest standardowe, zamierzam napisać pytanie na ten temat.

+0

ya Myślę, że jest to problem z implementacją kompilatora, ponieważ VC go nie wyświetla. – ganeshran

Powiązane problemy