paulo1205
(usa Ubuntu)
Enviado em 07/08/2017 - 04:36h
john1710 escreveu:
Fala galera, eu estudei POO em java e recentemente fui estudar c++, porem estava vendo um video em inglês, não entendo bem o idioma e tive muitas dificuldades, assim como coisas que nunca sequer imaginei que existisse. e vim pedir que se pudessem, esclarecem minhas duvidas.
durante o video me deparei com a criação dessa função.
template<typename T,typename... TArgs>
T& addComponent(TArgs&&... mArgs){}
minhas duvidas são:
1- quando eu crio o template, qual a diferença de usar class T e typename T,?
Nesse caso, nenhuma.
Quando o C++ introduziu
templates na linguagem, a forma de usar era sempre “
template <class T> definição_da_função_ou_classe_usando_T ”. Contudo, algumas pessoas achavam estranho usar “
class ” quando o argumento do
template era um tipo nativo, em vez de um tipo definido pelo usuário (classes, estruturas, uniões, enumerações etc.). Então o padrão de 1998 decidiu que, no contexto de
templates , as duas palavras poderiam ser usadas indistintamente, tanto para tipos nativos como para tipos definidos pelo usuário. (Aliás, uma diferença entre
templates do C++ e
Generics do Java é justamente poder ser usado com tipos nativos.)
As duas declarações abaixo são sinônimas, indicando uma função que recebe, por referência constante, dois argumentos do mesmo tipo, e devolve o valor do maior deles.
template <class T> T max(const T &x, const T &y){ return x<y? y: x; }
template <typename T> T max(const T &x, const T &y){ return x<y? y: x; }
Um outro tipo de argumento que pode ser passado para um
template é um valor constante de um tipo nativo. Nesse caso, em lugar de “
class ” ou “
typename ”, você usa a designação do nome do tipo. Por exemplo, você pode criar uma classe para representar matrizes em que a dimensão é um parâmetro inteiro amarrado em tempo de compilação, através de template. Isso permitiria ao compilador identificar operações ilegais com matrizes de dimensões incompatíveis.
// Classe que representa uma matriz.
template <unsigned ROWS, unsigned COLS> class Matrix { /* ... */ };
// Operador de soma de matrizes.
template <unsigned R, unsigned C> Matrix<R, C> operator +(const Matrix<R, C> &m1, const Matrix<R, C> &m2){ /* ... */}
// Operador de produto de matrizes.
template <unsigned R1, unsigned C1R2, unsigned C2> Matrix<R1, C2> operator *(const Matrix<R1, C1R2> &m1, const Matrix<C1R2, C2> &m2){ /* ... */}
// Exemplos de uso
void f(){
Matrix<3, 4> a, b, c;
Matrix<4, 5> d;
Matrix<3, 5> e;
c=a+b; // OK: todas as matrizes têm dimensões compatíveis com a soma.
d=a+b; // Erro de compilação: d não é compatível com o tipo retornado pela soma.
d=a*b; // Erro de compilação: a e b não têm dimensões compatíveis com o produto.
e=a*d; // OK: nº cols de a==nº lins de d, e dimensões de e são nº lins de a e nº cols de d.
}
2- o que significa reticencias.
Reticências existem desde priscas eras no C. Antigamente elas eram usadas apenas com funções, indicando que elas podiam receber uma quantidade variável de argumentos (VAL, de
variable argument list ).
Tal uso de funções com lista de argumentos variável também foi adotado pelo C++, por causa da compatibilidade com C, mas tinha um sério problema do ponto de vista do C++: a verificação de tipos não apenas não funciona com VALs, mas VALs ainda impõem regras para forçosamente modificar os tipos de alguns argumentos.
Uma parte das inconveniências do C podia ser contornada com polimorfismo, particularmente com sobrecarga de funções (i.e. funções com o mesmo nome, mas com argumentos diferentes). Mas o caso mais geral, em que a lista de argumentos podia realmente variar bastante na quantidade de argumentos, dependia de algo mais, e isso levou ao aparecimento de
templates com quantidade variável de argumentos, padronizada no C++11.
O caso que você trouxe é um caso de
template com lista de argumentos variável. Ela geralmente trabalha, no caso de
templates de classes. junto com especializações, e com sobrecarga de funções, no caso de
templates de funções e operadores.
3- e nesse caso dentro do parametro template, porque tem esse "typename... Targs" ?
Para indicar:
a) que há múltiplos argumentos para o
template daquele ponto em diante (“
... ”),
b) que tais argumentos designam tipos (“
typename ”), e
c) a forma de referir-se a esses elementos numa eventual expansão do VAL é através do nome “
Targs ”.
4- porque o retorno é "T&" o que é esse T&?
T é um dos argumentos do
template , que não faz parte da bloco de argumentos variáveis e que designa um tipo.
T& significa “referência para dado do tipo
T ”. Esse tipo
T& está sendo usado para designar o tipo de retorno de uma função chamada
addComponent () (ou seja, quando essa função for invocada, o resultado será do tipo “referência para valor do tipo
T ”).
5- e como paremetro de função, o que é "Targs&&... mArgs" denovo o que ser essas reticencias e esses dois &&??
Quando as reticências aparecem após o nome do argumento que designa o VAL do template, você indica uma expansão do VAL em seus componentes. No seu caso, como o nome
Targs é um VAL de tipos de dados, a expansão será uma outra VAL, só que agora para designar os objetos dos respectivos tipos da primeira VAL.
Talvez um exemplo ajude (N.B. eu sei que o exemplo é meio tosco, porque poderia muito bem ser feito sem
templates , e ainda mais sem VAL, e também porque introduzo uma referência desnecessária apenas para mostrar como ela aparece numa das etapas de expansão, e acabo não a usando no final; mesmo assim, creio que vai servir para entender como são feitas as expansões).
class generic_point { };
class point2d: public generic_point {
public:
point2d(double, double);
};
class point3d: public generic_point {
public:
point3d(double, double, double);
};
class point_list {
public:
void add(generic_point *pt);
/* bla, bla, bla */
};
template <class T, class... T_Components>
T *makepoint(const T_Components &... components){
return new T(components...);
}
void f(){
point_list pl;
pl.add(makepoint<point2d>(0.0, 1.0));
/*
1ª expansão: T=point2d, T_Components=“double, double”.
*/
/*
2ª expansão: cria função
“point2d *makepoint(const double &arg1, const double &arg2)” (arg1 e
arg2 são nomes fictícios, só para efeito de explicação).
components corresponde à lista formada pelos valores dos argumentos.
*/
/*
3ª expansão: expande components, chamando o construtor de point2d
com os valores dos argumentos “arg1, arg2”.
*/
pl.add(makepoint<point3d>(-1, 2.2, -3.3));
/*
1ª expansão: T=point3d, T_Components=“int, double, double” (o primeiro
argumento é do tipo int; a conversão de int para double será feita mais
tarde).
*/
/*
2ª expansão: cria função
“point3d *makepoint(const int &arg1, const double &arg2, const double &arg3)”
(arg1 arg2 e arg3 são nomes fictícios, só para efeito de explicação).
components corresponde à lista formada pelos valores dos argumentos.
*/
/*
3ª expansão: expande components, chamando o construtor de point3d
com os valores dos argumentos “arg1, arg2, arg3”. Como point3d só
tem um construtor com os três argumentos do tipo double e a conversão
de int para double pode ser feita automaticamente, o arg1 é convertido
para double neste ponto.
*/
}
O exemplo acima mostra que, na segunda expansão, eu gerei referências constantes (e.g. “const int &” e “const double &”). Se eu quisesse (e se fizesse sentido), eu poderia ter usado referências para
rvalues , indicadas pelo token
&& .
Referências para
rvalues são muito bem explicadas, quase que num passo-a-passo, e muito bem escrito, em
http://thbecker.net/articles/rvalue_references/section_01.html (em Inglês).
obs: eu li em alguns artigos que reticencias em c++ queria dizer que viriam varios parametros, se for verdade como eu vou tratar esses varios parametros dentro da função?
Geralmente o que acontece é que você tem uma parte dos argumentos do
template que é não-VAL, e outra parte que é VAL. Os dois usos mais comuns são:
1) Usar os argumentos não-VAL para escolher uma classe ou função específica, e fazer
forward de todos os argumentos VAL para o construtor dessa classe ou para a função escolhida (como eu fiz no exemplo acima).
2) Consumir de algum modo os argumentos não-VAL, e chamar recursivamente o mesmo template com os argumentos VAL, de modo que, nessa próxima chamada, uma parte dos argumentos que eram VAL passe a ser não-VAL, encurtando a lista de argumentos, até que ela fique vazia.