Eu já vi alguns exemplos de C ++ usando parâmetros de modelo (ou seja, modelos que usam modelos como parâmetros) para criar um design de classe baseado em políticas. Que outros usos essa técnica tem?
c++
templates
template-templates
Ferruccio
fonte
fonte
Respostas:
Eu acho que você precisa usar a sintaxe do modelo para passar um parâmetro cujo tipo é um modelo dependente de outro modelo como este:
Aqui
H
está um modelo, mas eu queria que essa função lidasse com todas as especializações deH
.NOTA : Estou programando c ++ há muitos anos e só precisei disso uma vez. Acho que é um recurso raramente necessário (é claro útil quando você precisar!).
Eu tenho tentado pensar em bons exemplos e, para ser sincero, na maioria das vezes isso não é necessário, mas vamos inventar um exemplo. Vamos fingir que
std::vector
não tem umtypedef value_type
.Então, como você escreveria uma função que pode criar variáveis do tipo certo para os elementos de vetores? Isso funcionaria.
NOTA :
std::vector
possui dois parâmetros de modelo, tipo e alocador, portanto, tivemos que aceitar os dois. Felizmente, devido à dedução de tipo, não precisamos escrever explicitamente o tipo exato.que você pode usar assim:
ou melhor ainda, podemos apenas usar:
ATUALIZAÇÃO : Mesmo este exemplo artificial, embora ilustrativo, não é mais um exemplo surpreendente devido à introdução do c ++ 11
auto
. Agora a mesma função pode ser escrita como:é assim que eu preferiria escrever esse tipo de código.
fonte
template<template<class, class> class C, class T, class U> void f(C<T, U> &v)
f<vector,int>
e nãof<vector<int>>
.f<vector,int>
meiosf<ATemplate,AType>
,f<vector<int>>
meiosf<AType>
Na verdade, o caso de uso dos parâmetros do modelo é bastante óbvio. Depois que você descobrir que o C ++ stdlib possui um buraco de não definir operadores de saída de fluxo para tipos de contêineres padrão, você deve escrever algo como:
Então você descobriria que o código para vetor é o mesmo, pois forward_list é o mesmo, na verdade, mesmo para vários tipos de mapa, ainda é o mesmo. Essas classes de modelo não têm nada em comum, exceto a meta-interface / protocolo, e o uso do parâmetro template template permite capturar a semelhança em todas elas. Antes de continuar a escrever um modelo, vale a pena verificar uma referência para lembrar que os contêineres de sequência aceitam 2 argumentos de modelo - para o tipo de valor e o alocador. Enquanto o alocador estiver em falta, ainda devemos considerar sua existência em nosso operador de modelo <<:
Voila, que funcionará automaticamente para todos os contêineres de seqüência presentes e futuros que aderem ao protocolo padrão. Para adicionar mapas à mistura, seria necessário dar uma olhada na referência para observar que eles aceitam 4 parâmetros de modelo, portanto, precisaríamos de outra versão do operador << acima com o parâmetro de modelo de modelo 4-arg. Também veríamos que std: pair tenta ser renderizado com o operador 2-arg << para os tipos de sequência definidos anteriormente, para fornecer uma especialização apenas para std :: pair.
Btw, com C + 11, que permite modelos variados (e, portanto, deve permitir args de modelos variados), seria possível ter um único operador << para governar todos eles. Por exemplo:
Resultado
fonte
__PRETTY_FUNCTION__
, que, entre outras coisas, relata descrições de parâmetros de modelo em texto simples. clang faz isso também. Um recurso mais útil às vezes (como você pode ver).Aqui está um exemplo simples, retirado de 'Design C ++ moderno - Programação genérica e padrões de design aplicados' por Andrei Alexandrescu:
Ele usa classes com parâmetros de modelo de modelo para implementar o padrão de política:
Ele explica: Normalmente, a classe host já conhece ou pode deduzir facilmente o argumento do modelo da classe de política. No exemplo acima, o WidgetManager sempre gerencia objetos do tipo Widget, portanto, exigir que o usuário especifique o Widget novamente na instanciação da CreationPolicy é redundante e potencialmente perigoso.Neste caso, o código da biblioteca pode usar parâmetros de modelo para especificar políticas.
O efeito é que o código do cliente pode usar 'WidgetManager' de uma maneira mais elegante:
Em vez da maneira mais complicada e propensa a erros que uma definição sem argumentos de modelo de modelo teria exigido:
fonte
Aqui está outro exemplo prático da minha biblioteca de redes neurais convolucionais CUDA . Eu tenho o seguinte modelo de classe:
que na verdade implementa a manipulação de matrizes n-dimensionais. Há também um modelo de classe filho:
que implementa a mesma funcionalidade, mas na GPU. Ambos os modelos podem funcionar com todos os tipos básicos, como float, double, int, etc. E também tenho um modelo de classe (simplificado):
O motivo aqui para ter a sintaxe do modelo é porque posso declarar a implementação da classe
que terá pesos e entradas do tipo float e na GPU, mas o connection_matrix sempre será int, seja na CPU (especificando TT = Tensor) ou na GPU (especificando TT = TensorGPU).
fonte
Digamos que você esteja usando o CRTP para fornecer uma "interface" para um conjunto de modelos filhos; e o pai e o filho são paramétricos em outros argumentos do modelo:
Observe a duplicação de 'int', que é realmente o mesmo parâmetro de tipo especificado para os dois modelos. Você pode usar um modelo de modelo para DERIVED para evitar esta duplicação:
Observe que você está eliminando o fornecimento direto de outros parâmetros de modelo para o modelo derivado ; a "interface" ainda os recebe.
Isso também permite criar typedefs na "interface" que depende dos parâmetros de tipo, que serão acessíveis a partir do modelo derivado.
O typedef acima não funciona porque você não pode digitar um modelo não especificado. Isso funciona, no entanto (e o C ++ 11 tem suporte nativo para typedefs de modelo):
Infelizmente, você precisa de um derivado_interface_tipo para cada instanciação do modelo derivado, a menos que haja outro truque que ainda não aprendi.
fonte
derived
pode ser usado sem os seus argumentos de modelo, ou seja, a linhatypedef typename interface<derived, VALUE> type;
template <typename>
. Em certo sentido, você pode pensar nos parâmetros do modelo como tendo um 'metatipo'; o metatipo normal para um parâmetro de modelo é otypename
que significa que ele precisa ser preenchido por um tipo regular; otemplate
metatype significa que ele precisa ser preenchido com uma referência a um modelo.derived
define um modelo que aceita umtypename
parâmetro metatipado, para que ele se ajuste à conta e possa ser referenciado aqui. Faz sentido?typedef
. Além disso, você pode evitar a duplicataint
no seu primeiro exemplo, usando uma construção padrão como umavalue_type
no tipo DERIVED.typedef
problema do bloco 2. Mas o ponto 2 é válido, eu acho ... sim, isso provavelmente seria uma maneira mais simples de fazer a mesma coisa.Isto é o que eu encontrei:
Pode ser resolvido para:
ou (código de trabalho):
fonte
Na solução com modelos variados fornecidos pelo pfalcon, achei difícil realmente especializar o operador ostream para std :: map devido à natureza gananciosa da especialização variável. Aqui está uma pequena revisão que funcionou para mim:
fonte
Aqui está um generalizado de algo que acabei de usar. Estou publicando, já que é um exemplo muito simples e demonstra um caso de uso prático, juntamente com argumentos padrão:
fonte
Ele melhora a legibilidade do seu código, fornece segurança extra de tipo e economiza alguns esforços do compilador.
Digamos que você queira imprimir cada elemento de um contêiner, você pode usar o seguinte código sem o parâmetro do modelo
ou com o parâmetro de modelo de modelo
Suponha que você passe um número inteiro, digamos
print_container(3)
. No primeiro caso, o modelo será instanciado pelo compilador, que reclamará do uso dec
no loop for, o último não instanciará o modelo, pois nenhum tipo correspondente pode ser encontrado.De um modo geral, se sua classe / função de modelo foi projetada para manipular a classe de modelo como parâmetro de modelo, é melhor deixar claro.
fonte
Eu o uso para tipos com versão.
Se você possui um tipo com versão através de um modelo como
MyType<version>
, pode escrever uma função na qual pode capturar o número da versão:Portanto, você pode fazer coisas diferentes, dependendo da versão do tipo que está sendo transmitida, em vez de ter uma sobrecarga para cada tipo. Você também pode ter funções de conversão que recebem
MyType<Version>
e retornamMyType<Version+1>
, de uma maneira genérica, e até recomendam que elas tenham umaToNewest()
função que retorna a versão mais recente de um tipo de qualquer versão anterior (muito útil para logs que podem ter sido armazenados há algum tempo) mas precisam ser processados com a ferramenta mais recente de hoje).fonte