2015-05-29 4 views
9

Ich mag eine Vorlage haben, mit einem verschachtelten Wert, der durch eine gegebene initializer Funktion initialisiert werden soll:Spezialisieren Vorlage mit Funktionszeigern, die auf Template-Parametern hängen

template <typename T, T(INIT)()> struct Foo 
{ 
    T value = INIT(); 
}; 

Es kann auf diese Weise verwendet werden:

// Some random type only instanceable through factory() 
struct Bar 
{ 
    int bar{}; 
private: 
    // The only way to create a Bar is through factory() 
    friend Bar factory(); 
    Bar() {}; 
}; 

Bar factory() { return {}; } 

Foo<Bar, factory> foo; 

Aber, wenn keine Funktion vorgesehen ist, versuchen die Vorlage sollte die verschachtelte Wert, auf Standard-initialisieren, so habe ich versucht, die Vorlage zu spezialisieren:

template <typename T> struct Foo<T, nullptr> 
{ 
    T value{}; 
}; 

Die Idee ist es auf diese Weise zu nutzen:

struct Baz{}; 

Foo<Bar, factory> foo; // Nested Bar have Bar::bar initialized through factory function. 
Foo<Baz>   baz; // No factory function needed, nested Baz default-initialized. 

Aber ich gerade entdeckt, dass die Vorlage Teil-Spezialisierung Typen auf andere Vorlagentypen nicht verlassen können, der Fehler erhalte ich unten eingefügt wird:

error: type 'T (*)()' of template argument 'nullptr' depends on a template parameter template struct Foo


Gibt es eine Möglichkeit, mein Ziel zu erreichen? Es wäre schön, wenn es mit Template-Variablen wie gut funktioniert:

template <typename T, T(INIT)()> T Foo = INIT(); 
template <typename T>   T Foo<T, nullptr>{}; 

Zusätzliche Frage: Warum partielle Spezialisierungen nicht auf Template-Parameter verlassen können? Was ist der Grund für diese Einschränkung?

Antwort

2

Wenn es nur darum geht, eine Standardinitialisierung durchzuführen, wenn der zweite Vorlagenparameter fehlt, können Sie eine standardmäßige Vorlageninitialisierungsfunktion als Standardparameter bereitstellen.

template<typename T> 
    T do_default_assign() { 
     return T(); 
    };                  

template <typename T, T (INIT)() = do_default_assign<T> > struct Foo 
    { 
     T value = INIT(); 
    }; 

Dies ist jedoch leidet eine unnötige „Rückkehr nach Wert“ und Zuweisungsoperation, die für einige T.

+0

Akzeptiert dies und nicht die von @ Jarod42, weil es 1 Sekunde zuvor beantwortet wurde! –

3

Für Ihren Fall teuer oder unmöglich sein könnte, können Sie verwenden:

template <typename T> 
T default_construct() { return T{}; } 

template <typename T, T(INIT)() = &default_construct<T>> 
struct Foo 
{ 
    T value = INIT(); 
}; 

Und dann verwenden sie es mögen:

Foo<int> f; 
Foo<int, bar> b; 

Live demo

2

Sie können eine constructor Template-Funktion definieren, die als Standard einen Wert vom Typ Type und dann Konstruktor initialisiert werden:

template<typename Type, typename... Args> 
Type constructor(Args... args) { 
    return Type(std::forward<Args>(args)...); 
} 

und dann verwenden Sie es als Standard-Template-Argument für die Funktion:

template <typename T, T(INIT)() = constructor<T>> struct Foo 
{ 
    T value = INIT(); 
}; 

Live demo

+0

Dieser Ansatz sieht gut aus, weil es erlaubt, einen beliebigen Parameter an den Konstruktor zu übergeben, aber ein 'Konstruktor' nicht kompatibel mit' T (INIT)() 'ist nicht möglich, so am Ende: Weiterleitung aller' Args' zum 'Konstruktor 'macht keinen Sinn :( –

+0

@PaperBirdMaster True, aber jetzt haben Sie einen allgemeineren' Konstruktor', der eine nützliche Funktion ist, die auf jedem Konstruktor eines beliebigen Typs (für andere Zwecke) funktioniert, anstelle eines hartcodierten 'default_construct' oder 'do_default_assign', wie andere vorgeschlagen haben. – Shoe