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!
@Nawaz: Danke für den Tipp. Hier macht es keinen Unterschied. – akim
'declltype (b.fun (std :: vorwärts (args) ...))' in schleppenden Rückgabetyp funktioniert als * Expression SFINAE *, während weder 'auto' noch' decltype (auto) 'tut –
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