2013-10-20 5 views
12

Ich versuche, eine Klasse zu erstellen, die die Konstruktoren von anderen Klassen übernehmen sollte, aber ohne von diesen Klassen selbst zu erben.Wie verwendet man std :: enable_if, um einen variadischen Konstruktor bedingt auszuwählen?

An einem Punkt während der Initialisierung meiner Klasse möchte ich perfekte Weiterleitung verwenden, um ein Objekt des Typs zu erstellen, dessen Konstruktor den angegebenen Argumenten entsprach.

Mit Ausnahme des Standardkonstruktors ohne Argumente dürfen keine Mehrdeutigkeiten auftreten.

Dies ist mein Code:

#include <string> 

using namespace std; 

//NOTE: this class is just an example to demonstrate the problem 
class String { 
    public: 
     //default constructor to prevent ambiguity 
     String() {} 

     //construct from wstring 
     template<typename... Args> 
     String(enable_if<is_constructible<wstring, Args...>::value, Args>::type&&... args) : ws(forward<Args>(args)...) {} 

     //construct from string 
     template<typename... Args> 
     String(enable_if<is_constructible<string, Args...>::value, Args>::type&&... args) : s(forward<Args>(args)...) {} 
    private: 
     string s; 
     wstring ws; 
}; 

void foo(const String& string) { 
} 

int main() 
{ 
    foo(L"123"); 
    foo("123"); 
    return 0; 
} 

ich viele Dinge ausprobiert, aber ich kann es einfach nicht funktioniert bekommen.

  • Im aktuellen Ansatz enable_if nicht automatisch die Vorlage args abziehen (glaube ich)
  • Da ich den Konstruktor verwenden kann ich nicht enable_if auf den Rückgabewert verwenden
  • einen anderen Standard-Parameter für enable_if gewonnen Hinzufügen ‚t Arbeit, weil der Konstruktor ist variadische
  • Wenn ich entfernen enable_if aus den Funktionsargumente der Compiler über ungültige Überlastungen (natürlich) klagt

Gibt es eine elegante Möglichkeit, dieses Problem zu lösen?

Bearbeiten: Die eine implizite Konvertierung, die von der Norm erlaubt ist, sollte nicht in meiner Klasse auftreten.

Eine Lösung, die mit dem obigen Beispiel funktioniert, wäre, einen einzelnen variadischen Konstruktor zu definieren und die Argumente perfekt an eine konditionierte Initialisierungsfunktion weiterzuleiten. Ich möchte jedoch diesen Overhead vermeiden, da Member standardmäßig erstellt werden müssen und dies in anderen Fällen möglicherweise nicht funktioniert.

(Fühlen Sie sich frei, die Frage zu bearbeiten, wenn Dinge klarer gemacht werden)

+0

Gute Frage und ich habe keine Antwort. FWIW, * ohne * variadic templates [funktioniert] (http://coliru.stacked-crooked.com/a/c799960a369ccf7e) unter Verwendung von voreingestellten Parametern. –

+0

Ich fragte nahe Verwandte Frage auch in: http://StackOverflow.com/Questions/18700072/using-SFINAE-ToSelect-Different-Method-Implementations-in-Akla- se-Template.Eine Antwort war auch, stattdessen Spezialisierung zu verwenden :-) – Klaus

+0

Wie ich in einem Kommentar zu Klaus 'Lösung gesagt habe: Nur eine ** benutzerdefinierte Umwandlung wird implizit durchgeführt. Entweder vom String-Literal zu 'std :: basic_string' oder von einem' std :: basic_string' zu Ihrem 'String'-Typ, aber nicht beides. – dyp

Antwort

2

ich das Problem und die Lösung kann nicht verstehen. Wenn ich zwei verschiedene Typen für eine Methode oder einen Konstruktor verwenden möchte, kann ich einfach beide Typen schreiben. Es ist daher nicht nötig, eine Vorlage mit SFINAE zu haben! Dies kann einfach durch Spezialisierung erfolgen.

class A 
{ 
    public: 
    template <typename ... Args> 
    A(const wstring &, Args ...); 

    template <typename ... Args> 
    A(const string &, Args ...); 
}; 

eine Vorlage haben für nicht wirklich nur eine Art, die genaue Übereinstimmung :-)

eine Vorlage semantisch ist

Nachdem Sie Ihren Kommentar zu lesen bekam ich diese Lösung:

class foo 
{ 
    public: 
     template <typename ... Args> 
     foo(const string &&x, Args ...) { cout << "Using string" << endl; } 

     template <typename ... Args> 
     foo(const wstring &&x, Args ...) { cout << "Using wstring" << endl; } 
}; 

int main() 
{ 
    foo("123"); 
    foo(L"123"); 

    foo("123", 1); 
    foo(L"123", 1.11); 
    return 0; 
} 

und diese Rückkehr wie erwartet:

Using string 
Using wstring 
Using string 
Using wstring 
+0

Wahr. Dies funktioniert tatsächlich in meinem aktuellen Fall und nicht zu bemerken, dass es ein bisschen peinlich ist. Das einzige Problem ist, dass es eine implizite Konvertierung verwendet, die ich versuchte zu vermeiden. – user1492625

+0

Eine implizite Konvertierung sollte nur stattfinden, wenn der Parameter nicht genau übereinstimmt. Wie ich Ihr Beispiel verstehe, haben Sie zwei explizit definierte Typen. Und zusätzlich, wenn Sie Ihre Typen mit einem enable_if angeben könnten, wenn Sie einen anderen Effekt haben: Wenn Ihr gegebener Typ nicht genau übereinstimmt, haben Sie keine Instanziierungen von irgendwelchen der Vorlagen, die zu einem Kompilierungsfehler führen. – Klaus

+0

Es gibt eine implizite Konvertierung von den angegebenen String-Literalen zum Parametertyp (entweder 'wstring' oder' string'). Dies ist übrigens ein Beispiel, in dem ein by-value-sink-Argument (anstelle von by-const-ref) leistungsfähiger ist, wenn Sie zusätzlich keine rvalue-ref-Überladungen bereitstellen. – dyp