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.