Verständnis der (einfachen?) partiellen C++-Template-Spezialisierung

Lesezeit: 9 Minuten

Verstandnis der einfachen partiellen C Template Spezialisierung
Dan

Notiz: Dies scheint ein Repost eines Problems zu sein: C++ – Überladen Sie eine Klassenmethode mit einer Teilspezifikation dieser Methode

Ich habe ein Problem, das ich mit der C++-Template-Spezialisierung habe, auf einen einfachen Fall reduziert.

Es besteht aus einer einfachen Template-Klasse mit 2 Parametern Thingwo ich mich spezialisieren möchte Thing<A,B>::doSomething() zum B=int.

#include <cstdio>

// A 3-parameter template class.
template <class A, class B>
class Thing
{
public:
    Thing(A a, B b) : a_(a), b_(b) {}
    B doSomething();
private:
    A a_;
    B b_;
};

// The generic case works as expected.
template <class A, class B>
B Thing<A,B>::doSomething()
{
    return b_;
}

// This specialization does not work!
template <class A>
int Thing<A,int>::doSomething()
{
    return b_+1;
}

int main() {
    // Setup our thing.
    Thing<double,int> thing(1.0,2);
    // This doesn't compile - but works with the generic case.
    printf("Expecting 3, and getting %i\n", thing.doSomething());
    // Clean up.
    return 0;
}

Unglücklicherweise, g++ beendet sich mit dem Fehler:

partial_specialization.cpp:30: error: invalid use of incomplete type ‘class Thing<A, int>’
partial_specialization.cpp:8: error: declaration of ‘class Thing<A, int>’

Die clang++ Compiler ist etwas ausführlicher, hat aber das gleiche Problem:

partial_specialization.cpp:30:19: error: nested name specifier 'Thing<A, int>::' for declaration does not
      refer into a class, class template or class template partial specialization
int Thing<A,int>::doSomething()
    ~~~~~~~~~~~~~~^
partial_specialization.cpp:32:12: error: use of undeclared identifier 'b_'
    return b_+1;
           ^
2 errors generated.

Ich habe gelesen und verstanden, dass partielle Template-Spezialisierungen auf Funktionen nicht zulässig sind – aber ich dachte, ich würde mich teilweise auf Klassen von spezialisieren Thing in diesem Fall.

Irgendwelche Ideen?

Was ich getan habe: Eine Problemumgehung, die sich aus dem Link ergibt, der von der akzeptierten Antwort bereitgestellt wird:

template< class T >
inline T foo( T const & v ) { return v; }

template<>
inline int foo( int const & v ) { return v+1; }

// The generic case works as expected.
template <class A, class B>
B Thing<A,B>::doSomething()
{
    return foo(b_);
}

Verstandnis der einfachen partiellen C Template Spezialisierung
Nawaz

Die teilweise Spezialisierung einer Funktionsvorlage, sei es eine Mitgliedsfunktionsvorlage oder eine eigenständige Funktionsvorlage, ist im Standard nicht zulässig:

template<typename T, typename U> void f() {} //okay  - primary template
template<typename T> void f<T,int>() {}      //error - partial specialization
template<> void f<unsigned char,int>() {}    //okay  - full specialization

Aber Sie können die Klassenvorlage selbst teilweise spezialisieren. Sie können so etwas tun:

template <class A>
class Thing<A,int>  //partial specialization of the class template
{
    //..
    int doSomething();
};

template <class A>
int Thing<A,int>::doSomething()  { /* do whatever you want to do here */ }

Beachten Sie, dass, wenn Sie eine Klassenvorlage teilweise spezialisieren, die Vorlagenparameterliste der Mitgliedsfunktion (in ihrer Definition außerhalb der Klasse) muss passen die Template-Parameterliste der partiellen Spezialisierung des Klassen-Templates. Das heißt, für die obige partielle Spezialisierung der Klassenvorlage können Sie dies nicht definieren:

template <class A>
int Thing<A,double>::doSomething(); //error

Dies ist nicht zulässig, da die Template-Parameterliste in der Funktionsdefinition nicht mit der Template-Parameterliste der partiellen Spezialisierung des Klassen-Templates übereinstimmt. §14.5.4.3/1 aus dem Standard (2003) sagt:

Die Vorlagenparameterliste eines Members einer partiellen Spezialisierung einer Klassenvorlage soll passen die Template-Parameterliste der partiellen Spezialisierung des Klassen-Templates.[…]

Lesen Sie dazu meine Antwort hier:

C++ – Überladen Sie eine Klassenmethode mit einer Teilspezifikation dieser Methode


Was ist also die Lösung? Würden Sie Ihre Klasse zusammen mit all der sich wiederholenden Arbeit teilweise spezialisieren?

Eine einfache Lösung wäre die Arbeitsdelegation, anstatt das Klassen-Template teilweise zu spezialisieren. Schreib ein eigenständige Funktionsvorlage und spezialisieren Sie diese als:

template <class B>
B doTheActualSomething(B & b) { return b;  }

template <>
int doTheActualSomething<int>(int & b) { return b + 1; }

Und rufen Sie dann diese Funktionsvorlage auf doSomething() Mitgliedsfunktion als:

template <class A, class B>
B Thing<A,B>::doSomething() { return doTheActualSomething<B>(b_); }

Da in Ihrem speziellen Fall doTheActualSomething muss den Wert kennen nur eine Mitglied, nämlich b_ist die obige Lösung in Ordnung, da Sie den Wert als Argument an die Funktion übergeben können, deren Typ die Vorlage ist Art Streit Bund Spezialisierung für int ist eine vollständige Spezialisierung möglich.

Aber stellen Sie sich vor, wenn es auf mehrere Mitglieder zugreifen muss, Art von jedem hängt von der Vorlage ab Art argument-list, dann würde das Definieren einer eigenständigen Funktionsvorlage das Problem nicht lösen, da es jetzt mehr als eine geben wird Art -Argument für die Funktionsvorlage, und das können Sie nicht teilweise spezialisieren Sie die Funktion für nur, sagen wir, eine Art (da es nicht erlaubt ist).

In diesem Fall können Sie also stattdessen ein Klassen-Template definieren, das eine statische Nicht-Template-Member-Funktion definiert doTheActualSomething. Hier ist, wie:

template<typename A, typename B>
struct Worker
{
   B doTheActualSomething(Thing<A,B> *thing)
   {
      return thing->b_;
   }
};

//partial specialization of the class template itself, for B = int
template<typename A>
struct Worker<A,int>
{
   int doTheActualSomething(Thing<A,int> *thing)
   {
      return thing->b_ + 1;
   }
};

Beachten Sie, dass Sie verwenden können thing Zeiger für den Zugriff auf ein beliebiges Mitglied der Klasse. Wenn es natürlich auf private Mitglieder zugreifen muss, dann musst du das machen struct Worker ein Freund von Thing Klassenvorlage, als:

//forward class template declaration
template<typename T, typename U> struct Worker

template <class A, class B>
class Thing
{
    template<typename T, typename U>  friend struct Worker; //make it friend
   //...
};

Delegieren Sie nun die Arbeit an den Freund als:

template <class A, class B>
B Thing<A,B>::doSomething()
{
    return Worker<A,B>::doTheActualSomething(this); //delegate work
}

Zwei Punkte sind hier zu beachten:

  • Bei dieser Lösung doTheActualSomething ist keine Member-Funktion Vorlage. Seine nicht einschließende Klasse, die Vorlage ist. Daher können wir teilweise Spezialisieren Sie die Klassenvorlage jederzeit, um den gewünschten Effekt zu erzielen teilweise Member-Funktionsvorlagen-Spezialisierung.
  • Da passieren wir this Zeiger als Argument auf die Funktion, können wir auf jedes Mitglied der Klasse zugreifen Thing<A,B>auch private Mitglieder, wie Worker<T,U> ist auch ein Freund.

Vollständige Online-Demo: http://www.ideone.com/uEQ4S


Jetzt besteht noch Verbesserungspotential. Jetzt alle Instanziierungen von Worker Klassenvorlagen sind Freunde aller Instanziierungen von Thing Klasse Vorlage. Wir können diese Viele-zu-Viele-Freundschaft also wie folgt einschränken:

template <class A, class B>
class Thing
{
    friend struct Worker<A,B>; //make it friend
   //...
};

Jetzt nur noch eine Instanziierung von Worker Klassenvorlage ist ein Freund einer Instanziierung von Thing Klasse Vorlage. Das ist Freundschaft auf Augenhöhe. Das ist, Worker<A,B> ist ein Freund von Thing<A,B>. Worker<A,B> ist KEIN Freund von Thing<A,C>.

Diese Änderung erfordert, dass wir den Code in einer etwas anderen Reihenfolge schreiben. Sehen Sie sich die vollständige Demo an, mit der gesamten Reihenfolge der Klassen- und Funktionsdefinitionen und allem:

http://www.ideone.com/6a1Ih

  • Gute Verbindung. Ich habe in Ihrem Zitat gelesen: “Eine Spezialisierung auf Klassenvorlagen ist eine eindeutige Vorlage.”. Bedeutet dies, dass meine Klassenspezialisierung alle erwarteten Mitgliedsdaten des Originals definieren muss Thing<A,B>? Wie ich es gelesen habe, ist die Antwort ja …

    – Dan

    26. Mai 2011 um 12:40 Uhr

  • Eine interessante Umgehung des Problems, alles neu definieren zu müssen, ist auch in dem von Ihnen gesendeten Link aufgeführt … siehe meine Verwendung von foo oben, angepasst an die Antwort von Alf P. Steinbach.

    – Dan

    26. Mai 2011 um 12:49 Uhr

  • Die Antwort ist falsch: Eine Spezialisierung der Mitgliedsfunktion ohne Spezialisierung der Klassenvorlage ist nicht zulässig ist falsch. Du kannst nicht teilweise spezialisieren Sie eine Member-Funktion in jedem Kontext, aber Sie können eine Member-Funktion einer Klassenvorlage spezialisieren. template <typename T> struct test { void foo() {} }; template <> void test<int>::foo() { std::cout << "specialized"; } ist vollkommen gültig.

    – David Rodríguez – Dribeas

    26. Mai 2011 um 13:06 Uhr

  • Die Problemumgehung ist auch falsch: Sie kann nicht eine Funktionsvorlage teilweise spezialisieren.

    – David Rodríguez – Dribeas

    26. Mai 2011 um 13:09 Uhr

  • Es ist immer noch falsch: Sie kann nicht eine Member-Funktion teilweise spezialisieren. Sie können eine generische (nicht spezialisierte) Member-Funktion einer partiellen Spezialisierung implementieren, aber Sie können eine Member-Funktion (oder eine Nicht-Member-Funktion) nicht teilweise spezialisieren je.

    – David Rodríguez – Dribeas

    26. Mai 2011 um 13:23 Uhr

1645593007 314 Verstandnis der einfachen partiellen C Template Spezialisierung
Johannes Schaub – litb

Dies ist ein sehr häufig auftretendes Problem, und es gibt überraschenderweise ein Problem einfach Lösung. Ich werde es in einem künstlichen Beispiel zeigen, weil es klarer ist, als Ihren Code zu verwenden, und Sie müssen es verstehen, um es an Ihren Code anzupassen

template<typename A, typename B>
struct TwoTypes { };

template<typename A, typename B>
struct X {
  /* forwards ... */
  void f() { fImpl(TwoTypes<A, B>()); }

  /* special overload for <A, int> */
  template<typename A1>
  void fImpl(TwoTypes<A1, int>) {
    /* ... */
  }

  /* generic */
  template<typename A1, typename B1>
  void fImpl(TwoTypes<A1, B1>) {
    /* ... */
  }
};

Funktionen explizit zu spezialisieren ist nie (fast nie?) der richtige Weg. In meiner Arbeit als Programmierer habe ich mich nie explizit auf ein Funktions-Template spezialisiert. Überlastung und Teilbestellung ist überlegen.

  • Der letzte Satz ist so wahr, ich wünschte, mehr Programmierer würden seinen Wert kennen. Ich erinnere mich an einen Artikel auf GotW, der das Problem mit spezialisierten Funktionsvorlagen ausführlich erklärt, kann aber gerade nicht danach suchen.

    – Xeo

    26. Mai 2011 um 18:06 Uhr

  • Außerdem könnten die Überladungen freie Funktionen sein und Sie könnten einfach übergeben this anstelle eines neuen Typs, wenn Sie keinen direkten Zugriff auf die Interna der Klasse benötigen. Nebenbei bemerkt, mit C++0x kann man ein Generikum erstellen template<class... Types> struct OrderingKey{}; anstelle einer Reihe zusätzlicher Strukturen für jede Anzahl von Typen.

    – Xeo

    26. Mai 2011 um 18:12 Uhr

832700cookie-checkVerständnis der (einfachen?) partiellen C++-Template-Spezialisierung

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy