2012-06-02 9 views
5

Angenommen, ich habe eine Funktion, die eine Nebenwirkung führt und dann eine Antwort zurück:Funktionszeiger und Rückgabetypkonvertierungen

int foo() 
{ 
    perform_some_side_effect(); 
    return 42; 
} 

Ich möchte foo an einen Funktionszeiger binden, aber ich bin nicht daran interessiert, in der Antwort, nur die Nebenwirkung:

void (*bar)() = foo; 

dies scheint jedoch eine Art Fehler zu sein:

error: invalid conversion from ‘int (*)()’ to ‘void (*)()’ 

Was ist der Grund für diesen Fehler? Warum erlaubt mir das Typsystem nicht, die Antwort zu ignorieren?


Auf einer Seite zur Kenntnis, es funktioniert, wenn ich die Funktionszeiger in einem std::function wickeln:

std::function<void()> baz = foo; 

Wie std::function (scheinbar) verwaltet diese Einschränkung in der Art System zu umgehen?

+1

Der rational ist nur, dass sie verschiedene Arten sind zu arbeiten. Sie können dies (auf eigene Gefahr) mit 'reininterpret_cast' überschreiben. –

+0

@StevenBurnap: Die Frage ist * warum * sie nicht konvertieren können. Es ist offensichtlich, dass sie unterschiedliche Typen sind. Ein 'float' ist auch kein' int', aber Sie können sie konvertieren. – Puppy

+0

Ich würde vermuten, dass es als allgemeine Regel gilt implizit Casting Pointer ist nicht erlaubt. –

Antwort

9

What is the rationale behind that error? Why doesn't the type system allow me to ignore the answer?

Der Grund dafür ist, dass die Arten unterschiedlich sind, und der erzeugte Code an dem Ort des Anrufs (durch die Funktionszeiger) unterschiedlich ist. Betrachten Sie eine Aufrufkonvention, bei der alle Argumente in den Stapel geschrieben werden und Platz für den Rückgabewert ebenfalls im Stapel reserviert ist. Wenn der Anruf über void (*)() geht, wird im Stapel kein Speicherplatz für den Rückgabewert reserviert, aber die Funktion (ohne zu wissen, wie sie aufgerufen wird) schreibt immer noch die 42 an den Speicherort, an dem der Anrufer Speicherplatz reserviert haben soll.

How does std::function (apparently) manage to circumvent this restriction in the type system?

Es tut es nicht. Es erstellt ein Funktionsobjekt, das den Aufruf an die eigentliche Funktion bindet. Er enthält ein Mitglied wie:

void operator()() const { 
    foo(); 
} 

Nun, wenn der Compiler den Aufruf foo verarbeitet sie weiß, was es eine Funktion Aufruf zu tun hat, die ein int zurückgibt und es tut so nach der Aufrufkonvention . Da die Vorlage nicht zurückkehrt, ignoriert sie einfach den Wert, der tatsächlich zurückgegeben wurde.

+1

+1: Vollständigste Antwort. –

1

std::function müssen nur quellkompatibel sein - das heißt, es kann eine neue Klasse generieren, die einen neuen Caling-Code generiert, der das Ergebnis ignoriert. Der Funktionszeiger muss binärkompatibel sein und kann diesen Job nicht ausführen. void(*)() und int(*)() zeigen auf genau denselben Code.

1

können Sie denken an std::function<> dies für Ihren speziellen Fall zu tun:

void __func_void() 
{ 
    foo(); 
} 

Es ist eigentlich ein bisschen komplizierter als das, aber der Punkt ist, dass es Template-Code zusammen mit Typ-Lösch erzeugt nicht kümmern sich um die Besonderheiten.

1

Zusätzlich zu dem, was andere bereits gesagt haben, benötigt der Aufrufer auch den Rückgabetyp, um zu wissen, welchen Destruktor er auf das Ergebnis aufrufen soll (der Rückgabewert ist möglicherweise temporär).


Leider ist es nicht so einfach wie

auto (*bar)() = foo; 

Obwohl GCC und Clang dies akzeptieren. Ich muss die Spezifikation überprüfen, um zu sehen, ob das tatsächlich richtig ist.

Update: Die Spezifikation sagt

The auto type-specifier signifies that the type of a variable being declared shall be deduced from its initializer or that a function declarator shall include a trailing-return-type.

Diese irreführend, wenn schnell gelesen werden kann, aber dies wird durch GCC und Klirren nur auf die Top-Level-declarator gelten umgesetzt. In unserem Fall ist dies ein Zeigerdeklarator. Der darin eingebettete Deklarator ist ein Funktionsdeklarator. Also ersetzen Sie einfach auto für void und dann wird der Compiler den Typ für Sie ableiten.


By the way, können Sie immer diese Arbeit manuell machen, aber es dauert einige Tricks es

template<typename FunctionType> 
struct Params; 

template<typename ...Params> 
struct Params<void(Params...)> { 
    template<typename T> 
    using Identity = T; 

    template<typename R> 
    static Identity<R(Params...)> *get(R f(Params...)) { 
    return f; 
    } 
}; 

// now it's easy 
auto bar = Params<void()>::get(foo); 
+0

Auch das kompiliert in VS2011 RC. automatischer Balken = Test; \t bar(); – Jagannath