O que o template <unsigned int N> significa?

121

Ao declarar um modelo, estou acostumado a ter este tipo de código:

template <class T>

Mas nesta questão , eles usaram:

template <unsigned int N>

Eu verifiquei se ele compila. Mas o que isso significa? É um parâmetro sem tipo? E se sim, como podemos ter um modelo sem qualquer parâmetro de tipo?

Igor Oks
fonte

Respostas:

148

É perfeitamente possível modelar uma classe em um número inteiro em vez de um tipo. Podemos atribuir o valor do modelo a uma variável ou, de outra forma, manipulá-lo de uma forma que faríamos com qualquer outro literal inteiro:

unsigned int x = N;

Na verdade, podemos criar algoritmos que avaliam em tempo de compilação (da 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
fonte
11
Você também pode usar o tipo em static constexpr intvez do seu enum. Portanto, o Factorial<0>modelo teria static constexpr int value = 1, e template <int N> struct Factorialpode terstatic constexpr int value = N * Factorial<N - 1>::value;
bobobobo
@bobobobo isso foi respondido antes do C ++ 11 e constexpr.
Justin Meiners
154

Sim, é um parâmetro sem tipo. Você pode ter vários tipos de parâmetros de modelo

  • Parâmetros de tipo.
    • Tipos
    • Modelos (apenas modelos de classes e alias, sem funções ou modelos de variáveis)
  • Parâmetros de não tipo
    • Ponteiros
    • Referências
    • Expressões constantes integrais

O que você tem aí é do último tipo. É uma constante de tempo de compilação (chamada de expressão constante) e é do tipo inteiro ou enumeração. Depois de pesquisar no padrão, tive que mover os modelos de classe para a seção de tipos - embora os modelos não sejam tipos. Mas eles são chamados de parâmetros de tipo com o propósito de descrever esses tipos. Você pode ter ponteiros (e também ponteiros de membro) e referências a objetos / funções que têm vínculo externo (aqueles que podem ser vinculados a partir de outros arquivos de objeto e cujo endereço é único em todo o programa). Exemplos:

Parâmetro de tipo de modelo:

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

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

Parâmetro de modelo inteiro:

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

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

Parâmetro de ponteiro de modelo (passando um ponteiro para uma função)

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 referência do modelo (passando um inteiro)

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

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

Parâmetro do modelo de modelo.

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;

Um modelo sem parâmetros não é possível. Mas um modelo sem nenhum argumento explícito é possível - ele tem argumentos padrão:

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

Vector<> test;

Sintaticamente, template<>é reservado para marcar uma especialização de modelo explícita, em vez de um modelo sem parâmetros:

template<>
struct Vector<3> {
    // alternative definition for SIZE == 3
};
Johannes Schaub - litb
fonte
Johannes, os modelos são arquivados em "tipos"? Achei que eles eram de que tipos podem ser feitos, mas não os próprios tipos?
sbi
@sbi veja a explicação: "Depois de pesquisar no padrão, tive que mover os modelos de classe para a seção de tipos - embora os modelos não sejam tipos. Mas eles são chamados de parâmetros de tipo para descrever esses tipos. " A nota de rodapé 126 em 14.1 / 2 diz isso. É apenas uma classificação feita para tornar os parâmetros não-tipo algo que declara um valor / referência e os parâmetros de tipo ser algo que declara um nome de tipo ou nome de modelo.
Johannes Schaub - litb
@ JohannesSchaub-litb então não há como digitar template com, digamos, std :: string? como a classe template <std :: string S> com algum contador estático para criar um id único para cada string diferente? hash string para int seria a única maneira, infelizmente, certo?
relaxxx
1
Adoraria ver esta resposta completada com objetos de membros de classe de modelo, ou seja, modelo <nome do tipo C, nome do tipo R, nome do tipo P1, nome do tipo P2> struct mystruct <R (C :: *) (P1, P2)>
Johnny Pauling
O trecho de código com SillyExamplenão pode ser compilado pelo GCC 4.8.4. O primeiro erro é the value of ‘flag’ is not usable in a constant expression. Existem outros erros também
HEKTO
17

Você cria um modelo para sua classe com base em um 'int não assinado'.

Exemplo:

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

Uma classe de modelo é como uma macro, só que muito menos nociva.

Pense em um modelo como uma macro. Os parâmetros do modelo são substituídos em uma definição de classe (ou função), quando você define uma classe (ou função) usando um modelo.

A diferença é que os parâmetros têm "tipos" e os valores passados ​​são verificados durante a compilação, como parâmetros para funções. Os tipos válidos são seus tipos C ++ regulares, como int e char. Ao instanciar uma classe de modelo, você passa um valor do tipo especificado e, em uma nova cópia da definição da classe de modelo, esse valor é substituído onde quer que o nome do parâmetro estivesse na definição original. Exatamente como uma macro.

Você também pode usar os tipos " class" ou " typename" para parâmetros (eles são realmente os mesmos). Com um parâmetro de um desses tipos, você pode passar um nome de tipo em vez de um valor. Assim como antes, em todos os lugares em que o nome do parâmetro estava na definição da classe de modelo, assim que você cria uma nova instância, torna-se o tipo que você passar. Este é o uso mais comum para uma classe de modelo; Todo mundo que sabe alguma coisa sobre modelos C ++ sabe como fazer isso.

Considere este código de exemplo de classe de modelo:

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

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

É funcionalmente igual a este código de macro:

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

Claro, a versão do modelo é um bilhão de vezes mais segura e mais flexível.

Jonathan
fonte