Como eu escrevi a biblioteca C ++ 11 padrão ou por que o impulso é tão assustador? Capítulo 4.1

Continuamos a aventura.

Resumo das peças anteriores


Devido a restrições na capacidade de usar compiladores C ++ 11 e, devido à falta de alternância, o boost queria escrever sua própria implementação da biblioteca C ++ 11 padrão na parte superior da biblioteca C ++ 98 / C ++ 03 fornecida com o compilador.

Static_assert , no exceto , countof foram implementados e, também, depois de considerar todos os recursos não padrão e de definição do compilador, surgiram informações sobre a funcionalidade suportada pelo compilador atual. É incluída sua própria implementação do nullptr , que é selecionada no estágio de compilação.

Chegou a hora de type_traits e todo esse "modelo especial de mágica".

Link para o GitHub com o resultado de hoje para impacientes e não leitores:

Compromissos e críticas construtivas são bem-vindos

Mergulhe no mundo do "template magic" C ++.

Sumário


1. Introdução
Capítulo 1. Viam supervadet vadens
Capítulo 2. #ifndef __CPP11_SUPPORT__ #define __COMPILER_SPECIFIC_BUILT_IN_AND_MACRO_HELL__ #endif
Capítulo 3. Localizando a Implementação NULLPTR Perfeita
Capítulo 4. C ++ Template Magic
.... 4.1 Começamos pequenos
.... 4.2 Sobre quantos erros milagrosos o log compila para nós
.... 4.3 Ponteiros e tudo-tudo-tudo
.... 4.4 O que mais é necessário para a biblioteca de modelos
Capítulo 5
...

Capítulo 4. C ++ Template Magic


Tendo terminado com as palavras-chave C ++ 11 e todos os "switches" dependentes da definição entre suas implementações, comecei a preencher type_traits . Na verdade, eu já tinha várias classes de modelos, semelhantes às padrão, que já trabalhavam em projetos há muito tempo e, portanto, restava trazer tudo isso da mesma forma, além de adicionar a funcionalidade que faltava.

imagem Honestamente, sou inspirado pela programação de modelos. Especialmente a percepção de que tudo isso é uma variedade de opções: cálculos, ramificação de código, condições, verificação de erros são realizados durante o processo de compilação e nada custa ao programa final em tempo de execução. E como os modelos em C ++ são essencialmente uma linguagem de programação completa de Turing , eu estava antecipando o quão elegante e relativamente fácil seria possível implementar a parte do padrão associado à programação em modelos. Mas, para destruir imediatamente todas as ilusões, direi que toda a teoria da completude de Turing é dividida em implementações concretas de modelos em compiladores. E essa parte da escrita da biblioteca, em vez de soluções elegantes e "truques" da programação de modelos, se transformou em uma luta feroz com os compiladores, enquanto cada um "entrou em colapso" à sua maneira, e é bom se ocorrer um erro interno do compilador, ou mesmo travar exceções não tratadas. O GCC (g ++) mostrou-se o melhor de tudo, que "mastigou" estoicamente todas as construções de modelos e apenas amaldiçoou (no caso) em locais onde havia uma falta de nome de tipo explícito.

4.1 Começando pequeno


Comecei com modelos simples para std :: integral_constant , std :: bool_constant e modelos pequenos semelhantes.

template<class _Tp, _Tp Val> struct integral_constant { // convenient template for integral constant types static const _Tp value = Val; typedef const _Tp value_type; typedef integral_constant<_Tp, Val> type; operator value_type() const { // return stored value return (value); } value_type operator()() const { // return stored value return (value); } }; typedef integral_constant<bool, true> true_type; typedef integral_constant<bool, false> false_type; template<bool Val> struct bool_constant : public integral_constant<bool, Val> {}; // Primary template. // Define a member typedef @c type to one of two argument types. template<bool _Cond, class _Iftrue, class _Iffalse> struct conditional { typedef _Iftrue type; }; // Partial specialization for false. template<class _Iftrue, class _Iffalse> struct conditional<false, _Iftrue, _Iffalse> { typedef _Iffalse type; }; 

Com base em condicionais, você pode inserir modelos convenientes para operações lógicas {"e", "ou", "not"} sobre os tipos (e todas essas operações são consideradas corretas no estágio de compilação! É ótimo, não é?):

 namespace detail { struct void_type {}; //typedef void void_type; template<class _B1 = void_type, class _B2 = void_type, class _B3 = void_type, class _B4 = void_type> struct _or_ : public conditional<_B1::value, _B1, _or_<_B2, _or_<_B3, _B4> > >::type { }; template<> struct _or_<void_type, void_type, void_type, void_type>; template<class _B1> struct _or_<_B1, void_type, void_type, void_type> : public _B1 { }; template<class _B1, class _B2> struct _or_<_B1, _B2, void_type, void_type> : public conditional<_B1::value, _B1, _B2>::type { }; template<class _B1, class _B2, class _B3> struct _or_<_B1, _B2, _B3, void_type> : public conditional<_B1::value, _B1, _or_<_B2, _B3> >::type { }; template<class _B1 = void_type, class _B2 = void_type, class _B3 = void_type, class _B4 = void_type> struct _and_; template<> struct _and_<void_type, void_type, void_type, void_type>; template<class _B1> struct _and_<_B1, void_type, void_type, void_type> : public _B1 { }; template<class _B1, class _B2> struct _and_<_B1, _B2, void_type, void_type> : public conditional<_B1::value, _B2, _B1>::type { }; template<class _B1, class _B2, class _B3> struct _and_<_B1, _B2, _B3, void_type> : public conditional<_B1::value, _and_<_B2, _B3>, _B1>::type { }; template<class _Pp> struct _not_ { static const bool value = !bool(_Pp::value); typedef const bool value_type; typedef integral_constant<bool, _not_::value == bool(true)> type; operator value_type() const { // return stored value return (value); } value_type operator()() const { // return stored value return (value); } }; } 

Três pontos merecem atenção aqui:

1) É importante sempre colocar um espaço entre os colchetes angulares ('<' e '>') dos modelos, pois antes do C ++ 11 não havia esclarecimentos no padrão sobre como interpretar '>>' e '<<' em códigos como _ou _ <_ B2, _ou _ <_ B3, _B4 >> e, portanto, quase todos os compiladores trataram isso como um operador de troca de bits, o que leva a um erro de compilação.

2) Em alguns compiladores (Visual Studio 6.0, por exemplo), havia um erro que consistia no fato de que era impossível usar o tipo de void como parâmetro de modelo. Para esses propósitos, um tipo vazio de void_type é introduzido na passagem acima para substituir o tipo de vazio onde o valor padrão do parâmetro do modelo é necessário.

3) Compiladores muito antigos (Borland C ++ Builder, por exemplo) tinham um tipo bool implementado de forma torta, que em algumas situações “repentinamente” se transformou em int ( true -> 1, false -> 0), bem como tipos de variáveis ​​estáticas constantes do tipo bool (e não apenas eles), se eles estavam contidos em classes de modelo. Por causa de toda essa bagunça, como resultado, para uma comparação completamente inofensiva no estilo my_template_type :: static_bool_value == false, o compilador pode facilmente emitir um encantamento que não pode converter 'tipo indefinido' em int (0) ou algo assim. Portanto, é necessário sempre tentar indicar explicitamente o tipo de valores para comparação, ajudando assim o compilador a determinar com quais tipos ele está lidando.


Adicione mais trabalho com valores const e voláteis . Primeiro, o remove_ trivialmente implementado ... onde simplesmente especializamos o modelo para determinados modificadores de tipo - se o tipo com o modificador entrar no modelo, o compilador deve, depois de examinar todas as especializações (lembrar o princípio SFINAE do capítulo anterior ) do modelo, selecionar o mais adequado (com indicação explícita do modificador desejado) :

 template<class _Tp> struct is_function; template<class _Tp> struct remove_const { // remove top level const qualifier typedef _Tp type; }; template<class _Tp> struct remove_const<const _Tp> { // remove top level const qualifier typedef _Tp type; }; template<class _Tp> struct remove_const<const volatile _Tp> { // remove top level const qualifier typedef volatile _Tp type; }; // remove_volatile template<class _Tp> struct remove_volatile { // remove top level volatile qualifier typedef _Tp type; }; template<class _Tp> struct remove_volatile<volatile _Tp> { // remove top level volatile qualifier typedef _Tp type; }; // remove_cv template<class _Tp> struct remove_cv { // remove top level const and volatile qualifiers typedef typename remove_const<typename remove_volatile<_Tp>::type>::type type; }; 

E então implementamos modelos add_ ... onde tudo já é um pouco mais complicado:

 namespace detail { template<class _Tp, bool _IsFunction> struct _add_const_helper { typedef _Tp const type; }; template<class _Tp> struct _add_const_helper<_Tp, true> { typedef _Tp type; }; template<class _Tp, bool _IsFunction> struct _add_volatile_helper { typedef _Tp volatile type; }; template<class _Tp> struct _add_volatile_helper<_Tp, true> { typedef _Tp type; }; template<class _Tp, bool _IsFunction> struct _add_cv_helper { typedef _Tp const volatile type; }; template<class _Tp> struct _add_cv_helper<_Tp, true> { typedef _Tp type; }; } // add_const template<class _Tp> struct add_const: public detail::_add_const_helper<_Tp, is_function<_Tp>::value> { }; template<class _Tp> struct add_const<_Tp&> { typedef _Tp & type; }; // add_volatile template<class _Tp> struct add_volatile : public detail::_add_volatile_helper<_Tp, is_function<_Tp>::value> { }; template<class _Tp> struct add_volatile<_Tp&> { typedef _Tp & type; }; // add_cv template<class _Tp> struct add_cv : public detail::_add_cv_helper<_Tp, is_function<_Tp>::value> { }; template<class _Tp> struct add_cv<_Tp&> { typedef _Tp & type; }; 

Aqui, processamos cuidadosamente os tipos de referência separadamente, para não perder o link. Além disso, não esqueceremos os tipos de funções que são impossíveis de tornar voláteis ou constantes em princípio; portanto, as deixaremos "como estão". Posso dizer que tudo isso parece muito simples, mas esse é exatamente o caso quando "o diabo está nos detalhes", ou melhor, "os bugs estão nos detalhes da implementação".

O final da primeira parte do quarto capítulo. Na segunda parte , falarei sobre o quão difícil é a programação de modelos para o compilador, e também haverá uma mágica mais legal sobre os modelos. Ah, e ainda - por que há muito tempo não é uma constante integral, de acordo com alguns compiladores até hoje.

Obrigado pela atenção.

Source: https://habr.com/ru/post/pt417547/


All Articles