¿Qué significa la plantilla <unsigned int N>?

121

Al declarar una plantilla, estoy acostumbrado a tener este tipo de código:

template <class T>

Pero en esta pregunta , usaron:

template <unsigned int N>

Verifiqué que se compila. Pero, ¿qué significa? ¿Es un parámetro que no es de tipo? Y si es así, ¿cómo podemos tener una plantilla sin ningún parámetro de tipo?

Igor Oks
fuente

Respuestas:

148

Es perfectamente posible modelar una clase en un número entero en lugar de un tipo. Podemos asignar el valor de la plantilla a una variable, o manipularlo de la forma en que lo haríamos con cualquier otro literal entero:

unsigned int x = N;

De hecho, podemos crear algoritmos que evalúen en tiempo de compilación (de 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
fuente
11
También puede usar type en static constexpr intlugar de su enum. Entonces la Factorial<0>plantilla tendría static constexpr int value = 1, y template <int N> struct Factorialpuede tenerstatic constexpr int value = N * Factorial<N - 1>::value;
bobobobo
@bobobobo esto fue respondido antes de C ++ 11 y constexpr.
Justin Meiners
154

Sí, es un parámetro que no es de tipo. Puede tener varios tipos de parámetros de plantilla

  • Tipo de parámetros.
    • Tipos
    • Plantillas (solo clases y plantillas de alias, sin funciones ni plantillas de variables)
  • Parámetros que no son de tipo
    • Punteros
    • Referencias
    • Expresiones constantes integrales

Lo que tienes ahí es del último tipo. Es una constante de tiempo de compilación (la denominada expresión constante) y es de tipo entero o enumeración. Después de buscarlo en el estándar, tuve que mover las plantillas de clase a la sección de tipos, aunque las plantillas no son tipos. Pero se les llama parámetros de tipo con el propósito de describir esos tipos de todos modos. Puede tener punteros (y también punteros a miembros) y referencias a objetos / funciones que tienen vínculos externos (aquellos a los que se pueden vincular desde otros archivos de objeto y cuya dirección es única en todo el programa). Ejemplos:

Parámetro de tipo de plantilla:

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

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

Parámetro de número entero de plantilla:

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

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

Parámetro de puntero de plantilla (pasar un puntero a una función)

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;

Parámetro de referencia de plantilla (pasando un número entero)

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

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

Parámetro de plantilla de plantilla.

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;

No es posible una plantilla sin parámetros. Pero una plantilla sin ningún argumento explícito es posible, tiene argumentos predeterminados:

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

Vector<> test;

Sintácticamente, template<>está reservado para marcar una especialización de plantilla explícita, en lugar de una plantilla sin parámetros:

template<>
struct Vector<3> {
    // alternative definition for SIZE == 3
};
Johannes Schaub - litb
fuente
Johannes, ¿las plantillas están archivadas en "tipos"? Pensé que eran los tipos de los que se pueden hacer, pero no los tipos en sí mismos.
sbi
@sbi vea la explicación: "Después de buscarlo en el estándar, tuve que mover las plantillas de clase a la sección de tipos, aunque las plantillas no son tipos. Pero se llaman parámetros de tipo con el propósito de describir esos tipos. ". La nota al pie 126 en 14.1 / 2 lo dice. Es solo una clasificación hecha para hacer que los parámetros que no son de tipo sean algo que declare un valor / referencia y que los parámetros de tipo sean algo que declare un nombre de tipo o un nombre de plantilla.
Johannes Schaub - litb
@ JohannesSchaub-litb, por lo que no hay forma de escribir la plantilla con digamos std :: string? como la clase template <std :: string S> con algún contador estático para crear una identificación única para cada cadena diferente? Hashing string a int sería la única forma desafortunadamente ¿verdad?
relaxxx
1
Me encantaría ver esta respuesta completada con objetos de miembro de clase de plantilla, es decir, plantilla <typename C, typename R, typename P1, typename P2> struct mystruct <R (C :: *) (P1, P2)>
Johnny Pauling
El fragmento de código con SillyExampleno puede ser compilado por GCC 4.8.4. El primer error es the value of ‘flag’ is not usable in a constant expression. También hay otros errores
HEKTO
17

Usted modela su clase basándose en un 'int sin firmar'.

Ejemplo:

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
fuente
15

Una clase de plantilla es como una macro, solo que mucho menos malvada.

Piense en una plantilla como una macro. Los parámetros de la plantilla se sustituyen en una definición de clase (o función), cuando define una clase (o función) utilizando una plantilla.

La diferencia es que los parámetros tienen "tipos" y los valores pasados ​​se verifican durante la compilación, como los parámetros de las funciones. Los tipos válidos son los tipos habituales de C ++, como int y char. Cuando crea una instancia de una clase de plantilla, pasa un valor del tipo que especificó y, en una nueva copia de la definición de la clase de plantilla, este valor se sustituye en el lugar donde estaba el nombre del parámetro en la definición original. Como una macro.

También puede utilizar " class" o "typename tipos " para los parámetros (en realidad, son los mismos). Con un parámetro de uno de estos tipos, puede pasar un nombre de tipo en lugar de un valor. Al igual que antes, en todas partes el nombre del parámetro estaba en la definición de la clase de plantilla, tan pronto como crea una nueva instancia, se convierte en el tipo que pase. Este es el uso más común de una clase de plantilla; Todo el que sepa algo sobre las plantillas de C ++ sabe cómo hacer esto.

Considere este código de ejemplo de clase de plantilla:

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

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

Funcionalmente es lo mismo que este código de uso de macros:

#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;
}

Por supuesto, la versión de plantilla es mil millones de veces más segura y flexible.

Jonathan
fuente