7

Ich habe eine Funktion, die jeden Typ durch universelle Referenz akzeptieren kann, und möchte es für bestimmte Typen überladen (von denen einige selbst templates sind, obwohl ich denke, dass das hier nicht wichtig ist). Leider kann ich die Überladungen nicht in der richtigen Reihenfolge auflösen.Überladungsauflösung mit universellen Referenzen

Ich hätte angenommen, dass die zweite Deklaration von foo vorzuziehen wäre, da es spezifischer ist (weniger Templates), obwohl es aussieht, dass mein Verständnis der Überladungsauflösung etwas fehlt. Interessanterweise ändert sich die zweite Deklaration, um X wertmäßig zu nehmen, es "gut, gut" zu drucken und es X durch nicht-konstante Referenz zu machen, lässt es "schlecht, gut" drucken. Das Entfernen der ersten Deklaration macht es offensichtlich "gut, gut", da es keine andere Wahl gibt.

Warum passiert das? Und vor allem, wenn der folgende Code nicht funktioniert, wie können Sie eine Funktion mit dieser Signatur überladen?

#include <iostream> 
#include <string> 

class X {}; 

template<typename T> 
inline std::string foo(T && rhs) { 
    return "bad"; 
} 

inline std::string foo(const X & rhs) { 
    return "good"; 
} 

int main() { 
    std::cout << foo(X()) << std::endl; 
    X x; 
    std::cout << foo(x) << std::endl; 
    return 0; 
} 

Edit:

Vielleicht eine umständliche Lösung dieses Problem ist es indirekt zu tun. Befreien Sie sich von der ersten Form von foo und verwenden Sie SFINAE, um zu überprüfen, ob eine gültige Überladung vorhanden ist. Sie ruft dann nicht foo_fallback auf.

+0

Ich habe mit einer Lösung für die Überlastung Teil kommen: http://mortoray.com/2013/06/03/overriding-the-broken-universal-reference-t/ –

+1

1 + hour [youtube video] (https://www.youtube.com/watch?v=T5swP3dr190) zu diesem genauen Problem, das auf diese Frage auf unterhaltsame Weise zurückführt. – Yakk

Antwort

3

Um Ihre question.comment zu Kerre Antwort zu beantworten, könnten Sie versuchen, SFINAE zu verwenden:

#include <type_traits> 
#include <string> 

template <class T> 
struct HasFooImpl_ { 
    template <typename C> 
    static std::true_type test(decltype(fooImpl(std::declval<C>()))*); 
    template <typename C> 
    static std::false_type test(...); 
    typedef decltype(test<T>(0)) type; 
}; 

template <typename T> 
using HasFooImpl = typename HasFooImpl_<T>::type; 

template <typename T> 
typename std::enable_if<HasFooImpl<T>::value, std::string>::type 
foo(T&& t) 
{ 
    return fooImpl(std::forward<T>(t)); 
} 

template <typename T> 
typename std::enable_if<!HasFooImpl<T>::value, std::string>::type 
foo(T&& t) 
{ 
    return "generic!"; 
} 

Sie haben würde eine Funktion fooImpl für jede Art zu implementieren, die Sie nicht wollen, genericly behandelt werden .

Die Implementierung war ein bisschen schwierig, ich versuchte zuerst enable_if<is_same<string, decltype(fooImpl(declval<C>()))>::value zuerst, aber für die Fallback die !is_same<>::value gab mir Compilerfehler, weil es versucht, den declltype als auch zu instanziieren.

Diese Implementierung hat eine Einschränkung, die Sie möglicherweise oder möglicherweise nicht verwenden möchten: wenn Tumwandelbar zu einem anderen Typ ist, der ein fooImpl definiert ist, wird diese Umwandlung in Kick

Sie können das Ganze sehen. Ding in Aktion hier: http://ideone.com/3Tjtvj

Update: , wenn Sie keine Typkonvertierungen lassen wollen, wird es tatsächlich einfacher:

#include <type_traits> 
#include <string> 

template <typename T> void fooImpl(T); 

template <typename T> 
using HasFooImpl = typename std::is_same<std::string, decltype(fooImpl(std::declval<T>()))>; 

template <typename T> 
typename std::enable_if<HasFooImpl<T>::value, std::string>::type 
foo(T&& t) 
{ 
    return fooImpl(std::forward<T>(t)); 
} 

template <typename T> 
typename std::enable_if<!HasFooImpl<T>::value, std::string>::type 
foo(T&& t) 
{ 
    return "generic!"; 
} 

Siehe http://ideone.com/miaoop

+0

Ich mag das. [Hier ist] (http://ideone.com/vkyScJ) eine andere Implementierung der gleichen Idee, aber mit Ausdruck SFINAE anstelle einer Hilfsklasse und 'enable_if' (der Nachteil ist, dass es eine andere Ebene der Indirektion erfordert). – jleahy

+0

Ich bevorzuge tatsächlich die Konvertierungssemantik des letzteren, und die Implementierung ist sehr ordentlich. Ausgezeichnet. – jleahy

2

Die Umwandlung X-const X mit T = X oder T = X & schlechter als die direkte Übereinstimmung der Templat-Überlastung betrachtet.

+0

Also, wie macht man die universelle Referenzversion auf spezifischere Versionen zurück? – jleahy

+2

Möchten Sie auch zu einem bestimmten Zeitpunkt zu Mittag essen? – jleahy

+0

@jleahy: Sie könnten * drei * nicht-vordefinierte Überladungen für 'X &', 'X const &' und 'X &&' machen, um alle Fälle abzudecken ... und ja :-) Bearbeiten: [siehe hier] (http : //ideone.com/KNqnRB). –