Pular para o conteúdo principal

Inferência de tipo no C++ moderno

Inferência de tipo é a dedução automática do tipo de dado a partir de uma expressão. No caso do C++ moderno a dedução é feita pelo compilador.

Os templates do C++ já possuiam um forma limitada de dedução de tipos, ou seja, você não precisa indicar o tipo de dado explicitamente como demonstrado no exemplo abaixo:



https://gist.github.com/fabiogaluppo/743932759542f8a11d50

Note que ao chamar a função template display, ela não foi parametrizável, pois o mecanismo de templates soube deduzir T a partir do argumento indicado na chamada da função. Porém como indicado anteriormente,  a inferência via templates é limitada e não tem a mesma abrangência e usabilidade da inferência de tipos introduzidas no C++ moderno com auto e decltype.

Inferência de tipo com auto

A inferência no C++ é muito simples. Uma vez que você sabe declarar uma variável, basta substituir o tipo da variável pela palavra chave auto. O contéudo a direita da declaração, logo após a inicialização ou  atribuição,  é uma expressão (variável, chamada de função, ...) que possui um tipo associado. O compilador utilizará a informação para "substituir" e indicar o valor da variável declarada com auto. Abaixo, três variáveis estão declaradas explicitamente com seus tipos e outras três utilizam auto para a dedução do tipo a partir da expressão a direita.

https://gist.github.com/fabiogaluppo/9223837880fbe0c777c5

O problema ou caracteristica da dedução de tipos com auto é que os qualificadores para constante e referência são ignorados, apenas o tipo de dado é absorvido. No exemplo a seguir, a variável y1 não será constante, mas a variável y2 será constante.

https://gist.github.com/fabiogaluppo/361c45bdfcaaff2f914d

Portanto, basta utilizar const e/ou & para qualificar a nova declaração:

https://gist.github.com/fabiogaluppo/659b242782b83ae85726

A inferência de tipo também é uma conveniência para poupar os esforços de digitação ou deixar as declarações menos redundantes, como no caso de um container na forma de dicionário:

https://gist.github.com/fabiogaluppo/812f6453072c97983fed

Inferência de tipo com decltype

Com decltype a utilização é similar ao auto, porém ela preserva as qualificações da expressão. É possível indicar uma expressão completa dentro do parâmetro de decltype.

https://gist.github.com/fabiogaluppo/c717c5a0189f577cdb1c

No entanto, na maioria das vezes seu uso com expressão não é  tão conveniente devido a redundância que desejamos evitar. Imagine digitar algo assim:
decltype(e + e + e + e + e) a3 = (e + e + e + e + e);

Isso não parece ser simples.

O C++ 11 vai até aqui, o C++ 14 estende seu mecanismo de inferência de tipo e possui uma combinação dos benefícios de auto e decltype, como veremos a seguir.

Inferência de tipo com decltype(auto) do C++ 14


No C++ 14, decltype(auto) é a inferência de tipo em sua plenitude - além da dedução do tipo ela preservará suas qualificações como constante e/ou referência se este for caso. Abaixo, um exemplo de uso evitando certas redundâncias de notação:

https://gist.github.com/fabiogaluppo/b2a4d68680bd039d445d

Veja a seguinte função que gera uma const std::string aleatória:

https://gist.github.com/fabiogaluppo/f91f41c2bcb7023e64c8

O código acima utiliza inferência com auto em duas ocasiões:

  • No linha 8, onde o bind retornará um functor baseado no objeto do tipo função (outro functor) da distribuição informada no primeiro argumento;

  • Na linha 11,  como uma referência de char, para que seja possível a atribuição na próxima sequência.


Como se trata de uma função que retornará um valor constante, no consumo desta função, ao usarmos o decltype(auto) para declarar a variável que receberá o resultado desta computação, teremos a seguinte resposta:

https://gist.github.com/fabiogaluppo/5055858a1bd78fb39b35

Como referência, aqui estão as execuções do exemplo deste post compilados com clang++ (7.0.2 (clang-700.1.81)), g++ (g++ (GCC) 5.3.0) e cl (Microsoft (R) C/C++ Optimizing Compiler Version 19.00.23506 for x64) respectivamente, todos eles possuem suporte ao C++ 14:

inference_in_clang

inference_in_gcc

inference_in_msvc

Fontes:
https://github.com/SimplyCpp/examples/blob/master/understanding_inference_program.cpp

Comentários

Postagens mais visitadas deste blog

Mestre Iota

Iota é a nona letra do alfabeto grego, ela é equivalente à letra i do nosso alfabeto. Por convenção ou hábito, utilizamos a letra i na programação para indicar algum tipo de incrementador, como por exemplo, em um for-loop . https://gist.github.com/fabiogaluppo/a23894ae743f7dd29274 Curiosamente, iota , como identificador, também é utilizado na programação para indicar uma sequência finita e consecutiva de números inteiros, como por exemplo, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]. Inclusive, originalmente na STL existia a função iota , inspirada pela linguagem de programação APL , você pode conferir neste link: http://www.sgi.com/tech/stl/iota.html

Valores Aleatórios Simplificados

A partir do C++ 11, foi introduzido o header <random>  com diversos facilitadores para suporte de geração de números aleatórios. A produção destes números é feita através da combinação de duas categorias de objetos: os geradores e os distribuidores. Os geradores, são responsáveis pela geração dos números, e os distribuidores são responsáveis pela transformação dos números gerados em algum tipo de distribuição de probabilidade.  Como por exemplo, uma distribuição normal (aquela da Gaussiana) ou uma distribuição de Pareto (aquela do 80-20). As opções não faltam, como você pode ver nas referências, por exemplo:   http://www.cplusplus.com/reference/random/ ou  http://en.cppreference.com/w/cpp/header/random .

Policy-based design: log writer

Policy-based design Vamos neste artigo dar mais uma pincelada no Policy-based design . Vamos fazer como exemplo uma classe de log. Como este é só um exemplo, não vamos considerar múltiplos parâmetros no log, mas somente uma string, assim não fugiremos do assunto. Uma das coisas mais importantes neste tipo de design é o desacoplamento. Ele é uma excelente alternativa ao uso de interfaces por duas razões: Não gera chamadas virtuais (ou um nível de indireção em tempo de execução) Duck typing ( https://pt.wikipedia.org/wiki/Duck_typing ) Eu gosto bastante desse tipo de design, já usado aqui: http://simplycpp.com/2016/02/05/leitura-de-configuracao-em-c/