2017-02-11 3 views
1

Angenommen, ich habe die folgende Matrizenschablonenklasse und es ist erforderlich, den Vektor entweder als 1 x RowSize- oder ColSize x 1-Matrix darzustellen (damit ich viele mit Vektoren kompatible Matrixoperatoren wiederverwenden kann: Multiplikation von 2 Matrizen, Matrix mit einem skalare usw.) multipliziert:Teilspezialisierung und SFINAE

template <class T, size_t ColumnSize, size_t RowSize> 
struct Matrix { 
    T [ColumnSize][RowSize]; 
} 

ich habe zwei Fragen:

1) Wenn ich mich nicht täusche ich, dass entweder durch partielle Spezialisierung oder mit SFINAE (zum Beispiel auf Matrix Methoden erreichen kann Aktivieren der Methode 'length', wenn entweder ColSize oder RowSize 1) ist. Was sind die Vor- und Nachteile der genannten Optionen?

2) Wenn ich mit der teilweisen Spezialisierung, wählen zu gehen ist es eine Möglichkeit, eine Spezialisierung für beide Zeilen- und Spaltenvektoren, statt diese zu definieren:

template <class T, size_t ColumnSize> 
struct Matrix<T, ColumnSize, 1> { 
    T length() const; 

    T [ColumnSize][RowSize]; 
} 

template <class T, size_t RowSize> 
struct Matrix<T, 1, RowSize> { 
    T length() const; 

    T [ColumnSize][RowSize]; 
} 
+0

sollten Sie mehr Kontext geben, sonst ist Ihre Frage "meinungsbasiert". –

+0

Wenn Sie nach Abstrichen und Vorteilen von beiden gefragt haben, denke ich, dass das besser wäre, da das leichter objektiv zu beantworten ist. –

+0

Empfehlung: Sie können die Parameter als 'Vorlage Klasse Matrix' mit 'enum Klasse Vektororientierung {Spaltenvektor, Zeilenvektor};' definieren. Dann würde es keine Zweideutigkeit geben, denke ich. –

Antwort

1

Es hängt wirklich davon ab, ob die Anforderung "Eine allgemeine Matrix darf keine Längenmethode haben" (dann sollte SFINAE oder Vererbung verwendet werden), oder "length darf nicht auf einer allgemeinen Matrix aufgerufen werden" (dann ist eine statische_Ausgabe innerhalb der length Stelle anwendbar). Eine dritte Option ist, nichts zu tun und length für generische Matrizen anwendbar zu machen, jedoch gibt es noch andere Operationen, die nur an Vektoren arbeiten.

Für "eine allgemeine Matrix muss keine Längenmethode haben". Um Platz zu sparen, verwende ich int und kürzere Symbolnamen. Anstelle von int_ sollten Sie std::integral_constant verwenden. Der int_-Wrapper wird aufgrund von Spracheinschränkungen benötigt, die eine Spezialisierung mit komplexeren Berechnungen verbieten, wenn der Parameter ein Nicht-Typparameter ist. Daher nehmen wir dem Paramer einen Typ und hüllen den Wert in ihn ein. Das Folgende verwendet nicht SFINAE, sondern Vererbung. Mit d() der Vector Mixing-Basisklasse können Sie jederzeit auf die Daten des Vektors innerhalb der Mischklasse zugreifen. Diese

template<int> struct int_; 

template<typename D, typename S> 
struct V { }; 

template<typename T, int A, int B> 
struct M : V<M<T, A, B>, int_<A * B>> { 
    T data[A][B]; 
}; 

template<typename T, int A, int B> 
struct V<M<T, A, B>, int_<A + B - 1>> { 
    int length() const { return A * B; } 

    M<T, A, B> *d() { return static_cast<M<T, A, B>*>(this); } 
    const M<T, A, B> *d() const { return static_cast<const M<T, A, B>*>(this); } 
}; 

ist jetzt

int main() { 
    M<float, 1, 3> m1; m1.length(); 
    M<float, 3, 1> m2; m2.length(); 
    // M<float, 3, 2> m3; m3.length(); error 
} 

Für "length nicht auf eine allgemeine Matrix genannt werden müssen", Sie "static_assert"

template<typename T, int A, int B> 
struct M { 
    int length() const { 
     static_assert(A == 1 || B == 1, "must not be called on a matrix!"); 
     return A * B; 
    } 

    T data[A][B]; 
}; 

Wählen Sie, was am besten geeignet ist

können
0

SFINAE kann eine Template-Deklaration nur basierend auf ihren eigenen Parametern deaktivieren. Es ist ein wenig unnatürlich, eine Nicht-Template-Memberfunktion wie length mit den Parametern der einschließenden Klasse zu deaktivieren. Die Technik sieht wie folgt aus:

template <class T, size_t RowSize, size_t ColumnSize> 
struct Matrix { 
    // SFINAE turns a non-template into a template. 
    // Introduce a fake dependency so enable_if resolves upon function call. 
    template< typename size_t_ = size_t > 
    static constexpr 
    // Now write the actual condition within the return type. 
    std::enable_if_t< RowSize == 1 || ColumnSize == 1 
    , size_t_ > length() const; 
     { return RowSize * ColumnSize; } 

    T [ColumnSize][RowSize]; 
} 

Wenn Sie diese Hässlichkeit können Magen, dann bekommt man genau das, was Sie wollen: eine Funktion des gewünschten Typs, die vollständig verschwindet, wenn die Bedingung nicht erfüllt ist. Keine andere Unterstützung wird benötigt.

Auf der anderen Seite wirkt sich die partielle Spezialisierung auf die gesamte Klassendefinition aus. Da es in der Regel schlechtes Design ist, die gesamte Klasse in jeder Teilspezialisierung zu duplizieren, wird Vererbung verwendet, wie Johannes es beschreibt.

Um nur eine Alternative zu seiner Antwort hinzuzufügen, kann SFINAE innerhalb einer Teilspezialisierung verwendet werden, um die clevere Algebra und das int_ Problem zu vermeiden.

// Add "typename = void" for idiomatic class SFINAE. 
template<size_t RowSize, size_t ColumnSize, typename = void> 
struct maybe_vector_interface { }; // Trivial specialization for non-vectors 

// Partial specialization for vectors: 
template<size_t RowSize, size_t ColumnSize> 
struct maybe_vector_interface< RowSize, ColumnSize, 
    std::enable_if_t< RowSize == 1 || ColumnSize == 1 > > { 
    static constexpr int length() const 
     { return RowSize * ColumnSize; } 
}; 

template<typename T, size_t RowSize, size_t ColumnSize> 
struct Matrix 
    : maybe_vector_interface<RowSize, ColumnSize> { 
    T data[RowSize][ColumnSize]; 
}; 
+0

Warum wird die Bewertung abgelehnt ?. – Potatoswatter