Was bedeutet Vorlage <unsigned int N>?

121

Wenn ich eine Vorlage deklariere, bin ich an diese Art von Code gewöhnt:

template <class T>

Aber in dieser Frage verwendeten sie:

template <unsigned int N>

Ich habe überprüft, ob es kompiliert wird. Aber was bedeutet es? Ist es ein Nicht-Typ-Parameter? Und wenn ja, wie können wir eine Vorlage ohne Typparameter haben?

Igor Oks
quelle

Antworten:

147

Es ist durchaus möglich, eine Klasse auf einer Ganzzahl anstatt auf einem Typ zu erstellen. Wir können den Vorlagenwert einer Variablen zuweisen oder ihn auf andere Weise so bearbeiten, wie wir es mit einem anderen ganzzahligen Literal tun könnten:

unsigned int x = N;

Tatsächlich können wir Algorithmen erstellen, die zur Kompilierungszeit ausgewertet werden (aus Wikipedia ):

template <int N>
struct Factorial 
{
     enum { value = N * Factorial<N - 1>::value };
};

template <>
struct Factorial<0> 
{
    enum { value = 1 };
};

// Factorial<4>::value == 24
// Factorial<0>::value == 1
void foo()
{
    int x = Factorial<4>::value; // == 24
    int y = Factorial<0>::value; // == 1
}
maxaposteriori
quelle
10
Sie können auch type static constexpr intanstelle von your verwenden enum. Also die Factorial<0>Vorlage hätte static constexpr int value = 1und template <int N> struct Factorialkann habenstatic constexpr int value = N * Factorial<N - 1>::value;
Bobobobo
@bobobobo dies wurde vor C ++ 11 und beantwortet constexpr.
Justin Meiners
153

Ja, es handelt sich um einen Parameter ohne Typ. Sie können verschiedene Arten von Vorlagenparametern verwenden

  • Geben Sie Parameter ein.
    • Typen
    • Vorlagen (nur Klassen- und Aliasvorlagen, keine Funktionen oder Variablenvorlagen)
  • Nicht typisierte Parameter
    • Zeiger
    • Verweise
    • Integrale konstante Ausdrücke

Was Sie dort haben, ist von letzter Art. Es ist eine Kompilierungszeitkonstante (sogenannter konstanter Ausdruck) und vom Typ Integer oder Enumeration. Nachdem ich es im Standard nachgeschlagen hatte, musste ich Klassenvorlagen in den Abschnitt Typen verschieben - obwohl Vorlagen keine Typen sind. Sie werden jedoch als Typparameter bezeichnet, um diese Arten dennoch zu beschreiben. Sie können Zeiger (und auch Elementzeiger) und Verweise auf Objekte / Funktionen haben, die eine externe Verknüpfung haben (solche, mit denen aus anderen Objektdateien verknüpft werden kann und deren Adresse im gesamten Programm eindeutig ist). Beispiele:

Parameter für den Vorlagentyp:

template<typename T>
struct Container {
    T t;
};

// pass type "long" as argument.
Container<long> test;

Template Integer Parameter:

template<unsigned int S>
struct Vector {
    unsigned char bytes[S];
};

// pass 3 as argument.
Vector<3> test;

Vorlagenzeigerparameter (Übergabe eines Zeigers an eine Funktion)

template<void (*F)()>
struct FunctionWrapper {
    static void call_it() { F(); }
};

// pass address of function do_it as argument.
void do_it() { }
FunctionWrapper<&do_it> test;

Vorlagenreferenzparameter (Übergabe einer Ganzzahl)

template<int &A>
struct SillyExample {
    static void do_it() { A = 10; }
};

// pass flag as argument
int flag;
SillyExample<flag> test;

Vorlagenvorlagenparameter.

template<template<typename T> class AllocatePolicy>
struct Pool {
    void allocate(size_t n) {
        int *p = AllocatePolicy<int>::allocate(n);
    }
};

// pass the template "allocator" as argument. 
template<typename T>
struct allocator { static T * allocate(size_t n) { return 0; } };
Pool<allocator> test;

Eine Vorlage ohne Parameter ist nicht möglich. Eine Vorlage ohne explizites Argument ist jedoch möglich - sie enthält Standardargumente:

template<unsigned int SIZE = 3>
struct Vector {
    unsigned char buffer[SIZE];
};

Vector<> test;

Syntaktisch template<>ist reserviert, um eine explizite Vorlagenspezialisierung anstelle einer Vorlage ohne Parameter zu markieren:

template<>
struct Vector<3> {
    // alternative definition for SIZE == 3
};
Johannes Schaub - litb
quelle
Johannes, werden Vorlagen unter "Typen" abgelegt? Ich dachte, sie wären die Typen, aus denen man sie machen kann, aber nicht die Typen selbst?
sbi
@sbi siehe die Erklärung: "Nachdem ich es im Standard nachgeschlagen hatte, musste ich Klassenvorlagen in den Typabschnitt verschieben - obwohl Vorlagen keine Typen sind. Sie werden jedoch als Typparameter bezeichnet, um diese Typen trotzdem zu beschreiben. ". In Fußnote 126 vom 14.1 / 2 heißt es. Es ist nur eine Klassifizierung, die vorgenommen wird, um Nicht-Typ-Parameter zu etwas zu machen, das einen Wert / eine Referenz deklariert, und Typparameter zu etwas, das einen Typ- oder Vorlagennamen deklariert.
Johannes Schaub - litb
@ JohannesSchaub-litb, also gibt es keine Möglichkeit, eine Vorlage mit beispielsweise std :: string einzugeben? wie die Klasse template <std :: string S> mit einem statischen Zähler, um eine eindeutige ID für jede andere Zeichenfolge zu erstellen? Hashing String zu Int wäre der einzige Weg leider richtig?
Relaxxx
1
Ich würde gerne sehen, dass diese Antwort mit Elementen von Vorlagenklassenmitgliedern vervollständigt wird, dh Vorlage <Typname C, Typname R, Typname P1, Typname P2> Strukturmystruktur <R (C :: *) (P1, P2)>
Johnny Pauling
Der Code mit SillyExamplekann von GCC 4.8.4 nicht kompiliert werden. Der erste Fehler ist the value of ‘flag’ is not usable in a constant expression. Es gibt auch andere Fehler
HEKTO
17

Sie templatisieren Ihre Klasse basierend auf einem 'unsigned int'.

Beispiel:

template <unsigned int N>
class MyArray
{
    public:
    private:
        double    data[N]; // Use N as the size of the array
};

int main()
{
    MyArray<2>     a1;
    MyArray<2>     a2;

    MyArray<4>     b1;

    a1 = a2;  // OK The arrays are the same size.
    a1 = b1;  // FAIL because the size of the array is part of the
              //      template and thus the type, a1 and b1 are different types.
              //      Thus this is a COMPILE time failure.
 }
Martin York
quelle
15

Eine Vorlagenklasse ist wie ein Makro, nur viel weniger böse.

Stellen Sie sich eine Vorlage als Makro vor. Die Parameter der Vorlage werden in eine Klassen- (oder Funktions-) Definition eingesetzt, wenn Sie eine Klasse (oder Funktion) mithilfe einer Vorlage definieren.

Der Unterschied besteht darin, dass die Parameter "Typen" haben und übergebene Werte während der Kompilierung überprüft werden, wie Parameter an Funktionen. Die gültigen Typen sind Ihre regulären C ++ - Typen wie int und char. Wenn Sie eine Vorlagenklasse instanziieren, übergeben Sie einen Wert des von Ihnen angegebenen Typs. In einer neuen Kopie der Vorlagenklassendefinition wird dieser Wert überall dort eingesetzt, wo der Parametername in der ursprünglichen Definition enthalten war. Genau wie ein Makro.

Sie können auch die Typen " class" oder " typename" für Parameter verwenden (sie sind wirklich gleich). Mit einem Parameter eines dieser Typen können Sie anstelle eines Werts einen Typnamen übergeben. Wie zuvor wird der Parametername überall in der Vorlagenklassendefinition, sobald Sie eine neue Instanz erstellen, zu dem Typ, den Sie übergeben. Dies ist die häufigste Verwendung für eine Vorlagenklasse. Jeder, der etwas über C ++ - Vorlagen weiß, weiß, wie das geht.

Betrachten Sie diesen Beispielcode für Vorlagenklassen:

#include <cstdio>
template <int I>
class foo
{
  void print()
  {
    printf("%i", I);
  }
};

int main()
{
  foo<26> f;
  f.print();
  return 0;
}

Es ist funktional dasselbe wie dieser Makro-verwendende Code:

#include <cstdio>
#define MAKE_A_FOO(I) class foo_##I \
{ \
  void print() \
  { \
    printf("%i", I); \
  } \
};

MAKE_A_FOO(26)

int main()
{
  foo_26 f;
  f.print();
  return 0;
}

Natürlich ist die Vorlagenversion milliardenfach sicherer und flexibler.

Jonathan
quelle