2014-11-03 9 views
24

Dank decltype als Rückgabetyp hat C++ 11 die Einführung von Dekoratoren sehr einfach gemacht. Betrachten wir zum Beispiel diese Klasse:Warum ändert der automatische Rückgabetyp die Überladungsauflösung?

struct base 
{ 
    void fun(unsigned) {} 
}; 

ich es mit zusätzlichen Features schmücken wollen, und da ich, dass mehrmals mit verschiedenen Arten von Dekorationen tun, ich zum ersten Mal eine decorator Klasse einführen, die einfach alles base weiterleitet. Im echten Code geschieht dies über std::shared_ptr, so dass ich Dekorationen entfernen und das "nackte" Objekt wiederherstellen kann, und alles ist templated.

#include <utility> // std::forward 
struct decorator 
{ 
    base b; 

    template <typename... Args> 
    auto 
    fun(Args&&... args) 
    -> decltype(b.fun(std::forward<Args>(args)...)) 
    { 
    return b.fun(std::forward<Args>(args)...); 
    } 
}; 

Perfect Forwarding und decltype sind einfach wunderbar. Im realen Code verwende ich tatsächlich ein Makro, das nur den Namen der Funktion benötigt, der Rest ist Standard.

Und dann kann ich eine derived Klasse einführen, die Funktionen mein Objekt hinzufügt (derived ungeeignet ist, vereinbart, aber es hilft, das Verständnis, dass derived is-a-Art base, wenn auch nicht über Vererbung).

struct foo_t {}; 
struct derived : decorator 
{ 
    using decorator::fun; // I want "native" fun, and decorated fun. 
    void fun(const foo_t&) {} 
}; 

int main() 
{ 
    derived d; 
    d.fun(foo_t{}); 
} 

Dann 14 C++ kam, mit Abzug Rückgabetyp, die Dinge auf eine einfachere Art und Weise zu schreiben erlaubt: Entfernen Sie die decltype Teil der Weiterleitungsfunktion:

struct decorator 
{ 
    base b; 

    template <typename... Args> 
    auto 
    fun(Args&&... args) 
    { 
    return b.fun(std::forward<Args>(args)...); 
    } 
}; 

Und dann es geht kaputt. Ja, zumindest nach den beiden GCC und Clang, dies:

template <typename... Args> 
    auto 
    fun(Args&&... args) 
    -> decltype(b.fun(std::forward<Args>(args)...)) 
    { 
    return b.fun(std::forward<Args>(args)...); 
    } 
}; 

ist dies nicht äquivalent (und das Problem ist nicht auto gegen decltype(auto)):

template <typename... Args> 
    auto 
    fun(Args&&... args) 
    { 
    return b.fun(std::forward<Args>(args)...); 
    } 
}; 

Die Überladungsauflösung scheint zu sein, völlig anders, und es endet wie folgt aus:

clang++-mp-3.5 -std=c++1y main.cc 
main.cc:19:18: error: no viable conversion from 'foo_t' to 'unsigned int' 
    return b.fun(std::forward<Args>(args)...); 
       ^~~~~~~~~~~~~~~~~~~~~~~~ 
main.cc:32:5: note: in instantiation of function template specialization 
     'decorator::fun<foo_t>' requested here 
    d.fun(foo_t{}); 
    ^
main.cc:7:20: note: passing argument to parameter here 
    void fun(unsigned) {} 
       ^

verstehe ich den Fehler: mein Anruf (d.fun(foo_t{})) passt nicht perfekt mit der Unterschrift von derived::fun, die eine const foo_t& nimmt, so dass die sehr eifrige decorator::fun tritt (wir wissen, wie Args&&... ist sehr ungeduldig, an alles zu binden, das nicht perfekt zusammenpasst). Also leitet es das an base::fun weiter, das nicht mit foo_t umgehen kann.

Wenn ich derived::fun ändern, um ein foo_t statt const foo_t& zu nehmen, dann funktioniert es wie erwartet, was zeigt, dass in der Tat das Problem hier ist, dass ein Wettbewerb zwischen derived::fun und decorator::fun ist.

Aber warum zum Teufel macht diese Show mit Rückführungsabzug ??? Und genauer warum wurde dieses Verhalten vom Ausschuss gewählt?

Um die Dinge einfacher, auf Coliru machen:

Dank!

+0

@Nawaz: Danke für den Tipp. Hier macht es keinen Unterschied. – akim

+7

'declltype (b.fun (std :: vorwärts (args) ...))' in schleppenden Rückgabetyp funktioniert als * Expression SFINAE *, während weder 'auto' noch' decltype (auto) 'tut –

+4

hinzuzufügen Piotr S.'s Kommentar: Das ist auch beabsichtigt. Damit SFINAE funktionieren kann, muss der Rückgabetyp verfügbar sein, ohne den Funktionskörper zu betrachten. Um das Zurückgeben von Lambdas zu ermöglichen, darf der Rückgabetyp nicht verfügbar sein, ohne den Funktionskörper zu betrachten. Es wurde eine Entscheidung getroffen, dass die Rückkehr von Lambdas wichtiger als SFINAE war. – hvd

Antwort

18

Gerade bei diesem Aufruf:

d.fun(foo_t{}); 

Sie einen temporären erstellen (das heißt R-Wert), es als Argument an die Funktion übergeben. Nun, was denkst du passiert?

  • Es erste VersucheArg&& auf das Argument binden, wie es rvalue aber aufgrund ungültiger Rückgabetyp Abzug (die wiederum aufgrund foo_t konvertieren kann nicht in unsigned int annehmen können, weil von denen b.fun(std::forward<Args>(args)...) Umdrehungen out zu ungültigen Ausdruck), diese Funktion wird abgelehnt, wenn Sie die decltype(expr) als Rückgabetyp verwenden, wie in diesem Fall SFINAE ins Bild kommt. Aber wenn Sie einfach auto verwenden, dann kommt SFINAE nicht ins Bild und der Fehler wird als ein schwerer Fehler klassifiziert, der zum Kompilierungsfehler führt.

  • Die zweite Überladung, die foo_t const& als Argument akzeptiert, wird aufgerufen, wenn SFINAE im ersten Fall arbeitet.

+1

Ah, ich hatte nicht bemerkt, dass das C++ 11 funktionierte, weil der Aufruf von 'base :: fun' abgelehnt wurde und _then_' abgeleitet :: fun' als zweite Wahl genommen wurde. Danke vielmals! Wie kommt es, dass 'declltype (auto)' das Problem nicht behebt? Sollte es sowohl in GCC als auch in Clang als Fehler angesehen werden? – akim

+1

OK, ich lese gerade Ihre Kommentare zu der Frage selbst, und das macht alles klar, vielen Dank! – akim