Template-metaprograming ou constexpr, um primer e comparação em C++ 17, parte 2

Mar 11 2023
Em C++, SFINAE (falha de substituição não é um erro) e constexpr são dois conceitos importantes que desempenham um papel fundamental na metaprogramação de modelo e avaliação em tempo de compilação, respectivamente. Ambos permitem a criação de códigos mais poderosos e eficientes, mas possuem diferentes casos de uso e limitações.

Em C++, SFINAE (falha de substituição não é um erro) e constexpr são dois conceitos importantes que desempenham um papel fundamental na metaprogramação de modelo e avaliação em tempo de compilação, respectivamente. Ambos permitem a criação de códigos mais poderosos e eficientes, mas possuem diferentes casos de uso e limitações. Neste artigo, explicaremos esses conceitos em profundidade, forneceremos exemplos de código e compararemos e contrastaremos seus pontos fortes e fracos.

SFINAE

SFINAE é um mecanismo que permite que um compilador exclua funções do conjunto de sobrecarga com base na possibilidade ou não de uma determinada expressão ser avaliada. Por exemplo, podemos usar SFINAE para criar uma função modelo que calcula o fatorial de um número, mas apenas para tipos integrais:

Neste código, estamos usando std::enable_ifpara habilitar ou desabilitar condicionalmente o modelo de função com base em se Té um tipo integral. Se Tnão for integral, std::enable_ifresultará em falha de substituição e o compilador removerá essa função do conjunto de sobrecarga. Isso evitará que ocorram erros durante a compilação, pois a função não será instanciada.

Também podemos usar o SFINAE para criar funções de modelo mais complexas que dependem de várias condições. Por exemplo, podemos criar uma função de modelo que aceite apenas tipos que tenham uma ou mais funções de membro específicas. Antes de fazer isso, precisamos apenas entender o utilitário std::declval . Ele permite criar objetos temporários de um determinado tipo sem um construtor padrão. Exemplo de cppreference.com que colei no compilador explorer :

Conforme acima, você pode ver que o struct NonDefaulttem o destruidor padrão excluído! Queremos comparar que os tipos deste último e o Defaultretorno de struct são os mesmos ao executar o método foo. Defaulttem um destruidor padrão implícito, então criamos um temporário Defaulte fooo chamamos. Passamos isso para decltype para deduzir o tipo e declarar uma variável n1 desse tipo (int) igual a 1. Agora queremos verificar se foona struct NonDefault retorna o mesmo tipo mas como não tem construtor padrão, como nós discutido acima, recorremos a std::declval aqui que nos retornará uma referência rvalue deNonDefaulte tornar possível chamar foo no objeto temporário. Fazemos o mesmo com std::decltype e atribuímos a variável n1à nossa recém-declarada variável n2. O std::coutirá imprimir os valores 1 para ambas as variáveis.

Agora vamos ao exemplo em si com std::enable_if que verifica se temos 2 métodos chamados begin()e end():

Neste código, estamos usando std::declvalo que permite criar objetos temporários do tipo Tsem construtor padrão e usar decltypepara determinar o tipo de suas funções begin()e membros. end()Precisamos usar decltype para usar declval , caso contrário isso não é permitido…

Estamos então usando std::is_samepara verificar se esses tipos são os mesmos. Se estiverem, o modelo de função é ativado, caso contrário, é excluído do conjunto de sobrecarga.

Uma limitação do SFINAE é que ele só funciona durante a dedução do argumento do modelo, o que significa que não pode ser usado para excluir funções condicionalmente depois que o conjunto de sobrecarga já foi criado. É aqui if constexprque entra.

se constexpr

if constexpré um novo recurso introduzido no C++17 que nos permite executar código condicionalmente em tempo de compilação, com base em uma expressão constante em tempo de compilação. Ao contrário do SFINAE, if constexprpode ser usado para impedir que o código seja totalmente compilado, mesmo após a criação do conjunto de sobrecarga.

Por exemplo, podemos usar if constexprpara criar uma função modelo que calcula o fatorial de um número, mas apenas para tipos integrais:

Neste código, estamos usando if constexprpara executar código condicionalmente com base em se Té um tipo integral. Se Tfor integral, a função executará o cálculo fatorial. Caso contrário, falhará em uma asserção estática, o que impedirá que o código seja compilado. Observe que std::is_integral_vé uma abreviação de std::is_integral<T>::value.

Também podemos usar if constexprpara criar funções de modelo mais complexas que dependem de várias condições. Por exemplo, podemos criar uma função de modelo que aceite apenas tipos que tenham função de membro begin()e end()como fizemos acima com std::enable_if :

No exemplo de código acima, estamos criando uma característica de tipo has_begin_endque verifica se um tipo Ttem funções de membro begine end. usamos std::declvalpara converter o tipo Tem um tipo de referência, o que nos permite chamar funções de membro dentro de decltypeexpressões sem ter que construir um objeto conforme discutido anteriormente. Em primeiro lugar, chamamos a função de membro begin()on std::declval<T>()e, se for bem-sucedida, descartamos o resultado usando o operador vírgula. Em seguida, tentamos chamar end(), std::declval<T>()que, se for bem-sucedido, obtemos o tipo de retorno using decltype. Observe que decltypeé obrigatório, pois só é possível chamar funções de membro em tipos de referência usando declvalinside decltype. Finalmente, se tudo isso for bem-sucedido, usamos void_tpara retornarvoid, caso contrário, os parâmetros de modelo void_tfalham na avaliação e a especialização não pode ser resolvida durante a pesquisa de nome.

Um exemplo onipresente que podemos usar também para contrastar os usos de S FINAE e constexpr , é implementar o algoritmo fatorial

Exemplo com SFINAE

No código acima, estamos usando SFINAE para excluir tipos não integrais do conjunto de sobrecarga da factorialfunção usando std::enable_ifcom o std::is_integral para validar que é um número.

Exemplo com if constexpr

Estamos usando if constexprpara executar condicionalmente o código de cálculo fatorial se Tfor do tipo integral. Se Tnão for um tipo integral, a função falhará com a asserção estática, o que impedirá que o código seja compilado.

Casos de uso e recomendações

SFINAE e if constexpr são ferramentas poderosas que podem ser usadas para criar código mais eficiente e expressivo. No entanto, eles têm diferentes casos de uso e limitações.

SFINAE é melhor usado quando você deseja excluir funções condicionalmente do conjunto de sobrecarga com base nas propriedades dos argumentos da função. Por exemplo, você pode usar o SFINAE para criar modelos de função que aceitam apenas determinados tipos ou que possuem determinadas propriedades. O SFINAE também é útil quando você deseja criar funções de modelo mais complexas que dependem de várias condições.

if constexpr, por outro lado, é melhor usado quando você deseja executar código condicionalmente em tempo de compilação, com base em expressões constantes de tempo de compilação. if constexpré especialmente útil quando você deseja criar um código mais conciso e legível, pois a condição faz parte do corpo da função em vez de ser espalhada pelos parâmetros do modelo. if constexprtambém é útil quando você deseja fornecer mensagens de erro mais informativas, pois as asserções estáticas podem ser usadas para fornecer informações mais detalhadas.

Ao decidir se deve usar SFINAE ou constexpr , considere o seguinte:

  • Use SFINAE quando desejar excluir funções condicionalmente com base nas propriedades dos argumentos da função.
  • Use if constexpr quando quiser executar código condicionalmente com base em expressões constantes de tempo de compilação.
  • Use SFINAE quando quiser criar funções de modelo mais complexas que dependem de várias condições.
  • Use if constexpr quando quiser criar um código mais conciso e legível ou quando quiser fornecer mensagens de erro mais informativas.
  • Considere as limitações de cada abordagem, como o fato de que SFINAE só funciona durante a dedução de argumento de modelo ou que, se constexpr requer suporte a C++17.

SFINAE através do uso de std::enable_if e if constexpr são dois conceitos importantes em C++ que permitem a criação de código eficiente e expressivo. SFINAE é melhor usado quando você deseja excluir funções condicionalmente com base nas propriedades dos argumentos da função, enquanto if constexpr é melhor usado quando você deseja executar código condicionalmente com base em expressões constantes de tempo de compilação. Aprendemos que poderíamos usar declval com delctype para verificar a existência de funções de membro específicas quando o tipo não tinha um construtor padrão. Ao entender as diferenças entre o SFINAE e o if constexprabordagens e seus respectivos pontos fortes e fracos, você pode tomar melhores decisões ao escrever funções de modelo e criar código mais eficiente e expressivo.

Nota: Para a parte 1, verifiquehttps://medium.com/@joao_vaz/template-meta-programming-in-c-17-primer-1b493a22d51

Imagem de <a href="https://www.freepik.com/free-vector/business-decisions-concept-illustration_11392273.htm#query=choice&position=26&from_view=search&track=sph">Imagem por storyset</a> no Freepik