Wie gebe ich einen Zeiger auf eine überladene Funktion an?

137

Ich möchte eine überladene Funktion an den std::for_each()Algorithmus übergeben. Beispielsweise,

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

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

Ich würde erwarten, dass der Compiler nach f()dem Iteratortyp aufgelöst wird. Anscheinend macht es das (GCC 4.1.2) nicht. Wie kann ich also angeben, welche f()ich möchte?

Davka
quelle
3
en.cppreference.com/w/cpp/language/overloaded_address
Ciro Santilli 17 冠状 病 六四 事件 法轮功

Antworten:

137

Sie können static_cast<>()angeben, welche fgemäß der vom Funktionszeigertyp implizierten Funktionssignatur verwendet werden sollen:

// 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)); 

Oder Sie können dies auch tun:

// 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

Wenn fes sich um eine Mitgliedsfunktion handelt, müssen Sie die in diesem Artikel von Dr. Dobb vorgestellte Lösung verwenden mem_funoder für Ihren Fall verwenden .

In silico
quelle
1
Vielen Dank! Ich habe jedoch immer noch ein Problem, wahrscheinlich aufgrund der Tatsache, dass ich f()Mitglied einer Klasse
bin
9
@the_drow: Die zweite Methode ist tatsächlich viel sicherer, wenn eine der Überladungen verschwindet, gibt die erste Methode stillschweigend undefiniertes Verhalten, während die zweite das Problem beim Kompilieren abfängt.
Ben Voigt
3
@BenVoigt Hmm, ich habe dies auf vs2010 getestet und konnte keinen Fall finden, in dem static_cast das Problem beim Kompilieren nicht erkennen würde. Es gab einen C2440 mit "Keine der Funktionen mit diesem Namen im Gültigkeitsbereich entspricht dem Zieltyp". Könntest Du das erläutern?
Nathan Monteleone
5
@ Nathan: Es ist möglich, dass ich daran gedacht habe reinterpret_cast. Meistens sehe ich dafür Abgüsse im C-Stil. Meine Regel ist nur, dass Casts auf Funktionszeiger gefährlich und unnötig sind (wie das zweite Code-Snippet zeigt, existiert eine implizite Konvertierung).
Ben Voigt
3
Für Mitgliederfunktionen:std::for_each(s.begin(), s.end(), static_cast<void (A::*)(char)>(&A::f));
Sam-W
29

Lambdas zur Rettung! (Hinweis: C ++ 11 erforderlich)

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

Oder verwenden Sie decltype für den Lambda-Parameter:

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

Mit polymorphen Lambdas (C ++ 14):

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

Oder durch Entfernen der Überladung eindeutig (funktioniert nur für freie Funktionen):

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

void scan(const std::string& s)
{
    std::for_each(s.begin(), s.end(), f_c);
}
Milleniumbug
quelle
Hurra für Lambdas! In der Tat eine hervorragende Lösung für das Problem der Überlastauflösung. (Ich dachte auch daran, entschied mich aber, es aus meiner Antwort herauszulassen, um das Wasser nicht zu trüben.)
Aldo
Mehr Code für das gleiche Ergebnis. Ich denke, dafür sind die Lambdas nicht gemacht.
Tomáš Zato - Wiedereinsetzung Monica
@ TomášZato Der Unterschied besteht darin, dass diese Antwort funktioniert und die akzeptierte nicht (für das von OP veröffentlichte Beispiel - Sie müssen auch verwenden mem_fnund bind, was übrigens auch C ++ 11 sind). Wenn wir wirklich pedantisch werden wollen, sind [&](char a){ return f(a); }es 28 Zeichen und static_cast<void (A::*)(char)>(&f)35 Zeichen.
Milleniumbug
1
@ TomášZato Da gehst du coliru.stacked-crooked.com/a/1faad53c4de6c233 nicht sicher, wie man das klarer macht
Milleniumbug
18

Warum funktioniert es nicht?

Ich würde erwarten, dass der Compiler nach f()dem Iteratortyp aufgelöst wird. Anscheinend macht es (gcc 4.1.2) es nicht.

Es wäre großartig, wenn das der Fall wäre! Es handelt sich jedoch for_eachum eine Funktionsvorlage, die wie folgt deklariert ist:

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

Der Vorlagenabzug muss zum UnaryFunctionZeitpunkt des Aufrufs einen Typ auswählen . Hat faber keinen bestimmten Typ - es ist eine überladene Funktion, es gibt viele fs mit jeweils unterschiedlichen Typen. Es gibt derzeit keine Möglichkeit for_each, den Prozess des Vorlagenabzugs zu unterstützen, indem angegeben wird, was fgewünscht wird. Daher schlägt der Vorlagenabzug einfach fehl. Damit der Vorlagenabzug erfolgreich ist, müssen Sie mehr Arbeit auf der Anrufseite leisten.

Generische Lösung zur Behebung

Ein paar Jahre hier und später C ++ 14. Anstatt eine zu verwenden static_cast(die den Abzug von Vorlagen durch "Fixieren" erfolgreich machen würde, was fwir verwenden möchten, aber Sie müssen die Überladungsauflösung manuell durchführen, um die richtige zu "reparieren"), möchten wir, dass der Compiler für uns funktioniert. Wir wollen feinige Argumente anrufen . Auf die allgemeinste Art und Weise ist das:

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

Das ist eine Menge zu tippen, aber diese Art von Problem tritt ärgerlich häufig auf, so dass wir das einfach in ein Makro einwickeln können (Seufzer):

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

und dann benutze es einfach:

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

Dies macht genau das, was Sie sich vom Compiler gewünscht haben - führen Sie eine Überladungsauflösung für den Namen fselbst durch und tun Sie einfach das Richtige. Dies funktioniert unabhängig davon, ob fes sich um eine freie Funktion oder eine Mitgliedsfunktion handelt.

Barry
quelle
7

Nicht um deine Frage zu beantworten, aber bin ich der einzige, der findet

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

sowohl einfacher als auch kürzer als die for_eachvon in silico in diesem Fall vorgeschlagene Alternative?

anon
quelle
2
wahrscheinlich, aber es ist langweilig :) auch, wenn ich Iterator verwenden möchte, um den Operator [] zu vermeiden, wird dies länger ...
Davka
3
@ Davidka Langweilig ist was wir wollen. Außerdem sind Iteratoren in der Regel nicht schneller (möglicherweise langsamer) als die Verwendung von op [, wenn dies Ihr Anliegen ist.
7
Algorithmen sollten für Schleifen bevorzugt werden, da sie weniger fehleranfällig sind und bessere Optimierungsmöglichkeiten bieten können. Irgendwo gibt es einen Artikel dazu ... hier ist er: drdobbs.com/184401446
AshleysBrain
5
@Ashley Bis ich einige objektive Statistiken über die "weniger fehleranfällige" sehe, sehe ich keinen Grund, es zu glauben. Und Meyers in dem Artikel scheint über Schleifen zu sprechen, die Iteratoren verwenden - ich spreche über die Effizienz von Schleifen, die KEINE Iteratoren verwenden - meine eigenen Benchmarks deuten darauf hin, dass diese bei der Optimierung geringfügig schneller sind - sicherlich nicht langsamer.
1
Hier bin ich, ich finde auch Ihre Lösung viel besser.
Peter
5

Das Problem hier scheint nicht die Überlastungsauflösung zu sein, sondern die Ableitung von Vorlagenparametern . Während die hervorragende Antwort von @In silico ein mehrdeutiges Überlastungsproblem im Allgemeinen löst, scheint es die beste Lösung zu sein, wenn std::for_each(oder ähnliches) die Vorlagenparameter explizit angegeben werden :

// 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" );
}
aldo
quelle
4

Wenn es Ihnen nichts ausmacht, C ++ 11 zu verwenden, finden Sie hier einen cleveren Helfer, der dem statischen Cast ähnlich (aber weniger hässlich als dieser) ist:

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

(Funktioniert für Elementfunktionen; es sollte offensichtlich sein, wie es für freistehende Funktionen geändert werden kann, und Sie sollten in der Lage sein, beide Versionen bereitzustellen, und der Compiler wählt die richtige für Sie aus.)

Vielen Dank an Miro Knejp für den Vorschlag: siehe auch https://groups.google.com/a/isocpp.org/d/msg/std-discussion/rLVGeGUXsK0/IGj9dKmSyx4J .

Matthew
quelle
Das Problem von OP besteht darin, dass ein überladener Name nicht an eine Funktionsvorlage übergeben werden kann und Ihre Lösung darin besteht, einen überladenen Namen an eine Funktionsvorlage zu übergeben. Dies ist genau das gleiche Problem.
Barry
1
@ Barry Nicht das gleiche Problem. Der Abzug von Vorlagenargumenten ist in diesem Fall erfolgreich. Es funktioniert (mit ein paar kleinen Änderungen).
Oktalist
@Oktalist Da Sie bereitstellen R, wird es nicht abgeleitet. Das wird in dieser Antwort auch nicht erwähnt.
Barry
1
@ Barry Ich biete nicht R, ich biete Args. Rund Twerden abgeleitet. Es ist wahr, die Antwort könnte verbessert werden. ( TIn meinem Beispiel gibt es jedoch keine , weil es kein Zeiger auf ein Mitglied ist, weil das nicht funktionieren würde std::for_each.)
Oktalist