Jak określić wskaźnik do przeciążonej funkcji?


137

Chcę przekazać przeciążoną funkcję do std::for_each()algorytmu. Na przykład,

class A {
    void f(char c);
    void f(int i);

    void scan(const std::string& s) {
        std::for_each(s.begin(), s.end(), f);
    }
};

Spodziewałbym się, że kompilator rozwiąże f()typ iteratora. Najwyraźniej to (GCC 4.1.2) tego nie robi. Jak więc mogę określić, co f()chcę?


Odpowiedzi:


137

Możesz użyć, static_cast<>()aby określić, którego fużyć zgodnie z sygnaturą funkcji implikowaną przez typ wskaźnika funkcji:

// Uses the void f(char c); overload
std::for_each(s.begin(), s.end(), static_cast<void (*)(char)>(&f));
// Uses the void f(int i); overload
std::for_each(s.begin(), s.end(), static_cast<void (*)(int)>(&f)); 

Możesz też to zrobić:

// The compiler will figure out which f to use according to
// the function pointer declaration.
void (*fpc)(char) = &f;
std::for_each(s.begin(), s.end(), fpc); // Uses the void f(char c); overload
void (*fpi)(int) = &f;
std::for_each(s.begin(), s.end(), fpi); // Uses the void f(int i); overload

Jeśli fjest to funkcja członkowska, musisz użyć mem_funlub w swoim przypadku użyć rozwiązania przedstawionego w tym artykule dr Dobba .


1
dzięki! Nadal mam jednak problem, prawdopodobnie ze względu na to, że jestem f()członkiem klasy (patrz edytowany przykład powyżej)
— davka

9
@the_drow: Druga metoda jest w rzeczywistości znacznie bezpieczniejsza, jeśli jedno z przeciążeń zniknie, pierwsza metoda po cichu daje niezdefiniowane zachowanie, podczas gdy druga wychwytuje problem w czasie kompilacji.
— Ben Voigt

3
@BenVoigt Hmm, przetestowałem to na vs2010 i nie mogłem znaleźć przypadku, w którym static_cast nie wykryłby problemu w czasie kompilacji. Dało to C2440 z "Żadna z funkcji o tej nazwie w zakresie nie pasuje do typu docelowego". Możesz wyjaśnić?
— Nathan Monteleone

5
@Nathan: Możliwe, o czym myślałem reinterpret_cast. Najczęściej widzę używane do tego odlewy w stylu C. Moja reguła jest taka, że ​​rzutowanie wskaźników funkcji jest niebezpieczne i niepotrzebne (jak pokazuje drugi fragment kodu, istnieje niejawna konwersja).
— Ben Voigt

3
Funkcje członkowskie:std::for_each(s.begin(), s.end(), static_cast<void (A::*)(char)>(&A::f));
— sam-w

29

Jagnięta na ratunek! (uwaga: wymagany C ++ 11)

std::for_each(s.begin(), s.end(), [&](char a){ return f(a); });

Lub używając decltype dla parametru lambda:

std::for_each(s.begin(), s.end(), [&](decltype(*s.begin()) a){ return f(a); });

Z lambdami polimorficznymi (C ++ 14):

std::for_each(s.begin(), s.end(), [&](auto a){ return f(a); });

Lub ujednoznacznij, usuwając przeciążenie (działa tylko w przypadku bezpłatnych funkcji):

void f_c(char i)
{
    return f(i);
}

void scan(const std::string& s)
{
    std::for_each(s.begin(), s.end(), f_c);
}

Hurra dla lambd! Rzeczywiście, doskonałe rozwiązanie problemu rozwiązywania problemu z przeciążeniem. (Też o tym myślałem, ale postanowiłem pominąć to w mojej odpowiedzi, aby nie zabłocić wody.)
— aldo

Więcej kodu dla tego samego wyniku. Myślę, że nie do tego zostały stworzone lambdy.
— Tomáš Zato - Przywróć Monikę

@ TomášZato Różnica polega na tym, że ta odpowiedź działa, a zaakceptowana nie (w przykładzie opublikowanym przez OP - musisz również użyć mem_fni bind, przy okazji. To również C ++ 11). Ponadto, jeśli chcemy uzyskać naprawdę pedantyczny, [&](char a){ return f(a); }jest 28 znaków i static_cast<void (A::*)(char)>(&f)ma 35 znaków.
— milleniumbug

1
@ TomášZato Proszę bardzo coliru.stacked-crooked.com/a/1faad53c4de6c233 nie wiem, jak to wyjaśnić
— milleniumbug

18

Dlaczego to nie działa

Spodziewałbym się, że kompilator rozwiąże f()typ iteratora. Najwyraźniej to (gcc 4.1.2) tego nie robi.

Byłoby wspaniale, gdyby tak było! Jednak for_eachjest to szablon funkcji zadeklarowany jako:

template <class InputIterator, class UnaryFunction>
UnaryFunction for_each(InputIterator, InputIterator, UnaryFunction );

Odliczenie szablonowe należy wybrać typ UnaryFunctionw momencie połączenia. Ale fnie ma określonego typu - jest to przeciążona funkcja, istnieje wiele ftypów, z których każdy ma inny typ. Obecnie nie ma sposobu, for_eachaby wspomóc proces odliczania według szablonu, określając, czego fchce, więc odliczenie szablonowe po prostu się nie udaje. Aby odliczenie szablonu zakończyło się sukcesem, musisz wykonać więcej pracy w witrynie wezwania.

Ogólne rozwiązanie tego problemu

Wskoczę tutaj kilka lat i C ++ 14 później. Zamiast używać a static_cast(co pozwoliłoby na pomyślną dedukcję szablonu przez „naprawienie”, którego fchcemy użyć, ale wymaga ręcznego rozwiązania problemu z przeciążeniem, aby „naprawić” poprawne), chcemy, aby kompilator działał za nas. Chcemy wywołać fkilka argumentów. W najbardziej ogólny sposób jest to:

[&](auto&&... args) -> decltype(auto) { return f(std::forward<decltype(args)>(args)...); }

To dużo do pisania, ale tego rodzaju problem pojawia się irytująco często, więc możemy po prostu zawrzeć to w makro (westchnienie):

#define AS_LAMBDA(func) [&](auto&&... args) -> decltype(func(std::forward<decltype(args)>(args)...)) { return func(std::forward<decltype(args)>(args)...); }

a potem po prostu go użyj:

void scan(const std::string& s) {
    std::for_each(s.begin(), s.end(), AS_LAMBDA(f));
}

Zrobi to dokładnie to, co chcesz, aby zrobił kompilator - wykona rozwiązanie przeciążenia na fsamej nazwie i po prostu zrób to, co trzeba. Będzie to działać niezależnie od tego, czy fjest to funkcja wolna, czy funkcja składowa.


7

Nie odpowiadam na twoje pytanie, ale czy tylko ja znajduję

for ( int i = 0; i < s.size(); i++ ) {
   f( s[i] );
}

prostsza i krótsza niż for_eachalternatywa sugerowana przez in silico w tym przypadku?


2
chyba, ale to nudne :) również jeśli chcę użyć iteratora, aby ominąć operator [], to się wydłuża ...
— davka

3
@Davka Chcemy nudy. Ponadto iteratory zwykle nie są szybsze (mogą być wolniejsze) niż użycie op [, jeśli o to ci chodzi.

7
Zamiast pętli for należy preferować algorytmy, ponieważ są one mniej podatne na błędy i mogą mieć większe możliwości optymalizacji. Jest gdzieś artykuł na ten temat ... tutaj jest: drdobbs.com/184401446
— AshleysBrain

5
@Ashley Dopóki nie zobaczę obiektywnych statystyk dotyczących „mniej podatnych na błędy”, nie widzę potrzeby w to wierzyć. A Meyers w artykule wydaje się mówić o pętlach, które używają iteratorów - mówię o wydajności pętli, których NIE używają iteratorów - moje własne testy porównawcze sugerują, że są one nieznacznie szybsze po optymalizacji - z pewnością nie wolniej.

1
Oto ja, ja również uważam Twoje rozwiązanie za znacznie lepsze.
— peterh - Przywróć Monikę

5

Wydaje się, że problemem tutaj nie jest rozwiązanie przeciążenia, ale w rzeczywistości dedukcja parametrów szablonu . Chociaż doskonała odpowiedź od @In silico rozwiąże ogólnie niejednoznaczny problem przeciążenia, wydaje się, że najlepszym rozwiązaniem w przypadku std::for_each(lub podobnego) rozwiązania jest jawne określenie parametrów szablonu :

// Simplified to use free functions instead of class members.

#include <algorithm>
#include <iostream>
#include <string>

void f( char c )
{
  std::cout << c << std::endl;
}

void f( int i )
{
  std::cout << i << std::endl;
}

void scan( std::string const& s )
{
  // The problem:
  //   error C2914: 'std::for_each' : cannot deduce template argument as function argument is ambiguous
  // std::for_each( s.begin(), s.end(), f );

  // Excellent solution from @In silico (see other answer):
  //   Declare a pointer of the desired type; overload resolution occurs at time of assignment
  void (*fpc)(char) = f;
  std::for_each( s.begin(), s.end(), fpc );
  void (*fpi)(int)  = f;
  std::for_each( s.begin(), s.end(), fpi );

  // Explicit specification (first attempt):
  //   Specify template parameters to std::for_each
  std::for_each< std::string::const_iterator, void(*)(char) >( s.begin(), s.end(), f );
  std::for_each< std::string::const_iterator, void(*)(int)  >( s.begin(), s.end(), f );

  // Explicit specification (improved):
  //   Let the first template parameter be derived; specify only the function type
  std::for_each< decltype( s.begin() ), void(*)(char) >( s.begin(), s.end(), f );
  std::for_each< decltype( s.begin() ), void(*)(int)  >( s.begin(), s.end(), f );
}

void main()
{
  scan( "Test" );
}

4

Jeśli nie masz nic przeciwko używaniu C ++ 11, oto sprytny pomocnik, który jest podobny do (ale mniej brzydki) rzutowania statycznego:

template<class... Args, class T, class R>
auto resolve(R (T::*m)(Args...)) -> decltype(m)
{ return m; }

template<class T, class R>
auto resolve(R (T::*m)(void)) -> decltype(m)
{ return m; }

(Działa dla funkcji składowych; powinno być oczywiste, jak zmodyfikować go, aby działał dla funkcji wolnostojących, a powinieneś być w stanie zapewnić obie wersje, a kompilator wybierze odpowiednią dla Ciebie.)

Podziękowania dla Miro Knejp za sugestię: zobacz także https://groups.google.com/a/isocpp.org/d/msg/std-discussion/rLVGeGUXsK0/IGj9dKmSyx4J .


Problem OP polega na tym, że nie można przekazać przeciążonej nazwy do szablonu funkcji, a Twoje rozwiązanie obejmuje przekazanie przeciążonej nazwy do szablonu funkcji? To jest dokładnie ten sam problem.
— Barry

1
@Barry To nie ten sam problem. W tym przypadku dedukcja argumentów szablonowych kończy się sukcesem. Działa (z kilkoma drobnymi poprawkami).
— Oktalist

@Oktalist Ponieważ zapewniasz R, nie jest to wydedukowane. Nie ma też o tym wzmianki w tej odpowiedzi.
— Barry

1
@Barry Nie zapewniam R, zapewniam Args. Ri Tsą wywnioskowane. To prawda, że ​​odpowiedź można poprawić. ( TJednak w moim przykładzie nie ma, ponieważ nie jest to wskaźnik do elementu członkowskiego, ponieważ to by nie zadziałało std::for_each.)
— Oktalist
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.