
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". Nas partes anteriores deste capítulo, examinamos minha implementação dos modelos básicos da biblioteca padrão e, nesta parte, falaremos sobre a combinação da técnica SFINAE com modelos e um pouco sobre geração de código.
Link para o GitHub com o resultado de hoje para impacientes e não leitores:
Compromissos e críticas construtivas são bem-vindos
Mais modelos de C ++ em cat.
Sumário
1. IntroduçãoCapítulo 1. Viam supervadet vadensCapítulo 2. #ifndef __CPP11_SUPPORT__ #define __COMPILER_SPECIFIC_BUILT_IN_AND_MACRO_HELL__ #endifCapítulo 3. Localizando a Implementação NULLPTR PerfeitaCapí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 modelosCapítulo 5
...
Capítulo 4. Modelo C ++ "mágico". Continuação
4.3 Ponteiros e tudo-tudo-tudo
Nesta fase, eu só conseguia obter informações sobre se o tipo é uma matriz para
std :: is_array e foi possível iniciar modelos para ponteiros. A implementação também foi trivial, mas não sem suposições.
Uma especialização de modelo simples para matrizes de um determinado comprimento "captura" todos os tipos de matrizes; no entanto, o problema surge com o tipo incompleto
T [] (uma matriz sem especificar o comprimento). O fato é que esse tipo não é definido por alguns compiladores (C ++ Builder) ao especializar um modelo, e ainda não encontrei uma solução universal aqui.
Depois que a biblioteca foi “ensinada” a definir tipos internos, alinhamento na memória de tipos, trabalhar com modificadores de tipos e outras coisas básicas através de modelos em tempo de compilação, chegou a hora de apontadores e referências.

No C ++, dois grupos de ponteiros podem ser distinguidos - ponteiros para membros da classe e ponteiros para outros objetos. Por que essa separação é importante para a implementação adicional da biblioteca padrão? O fato é que os ponteiros para os alunos têm uma diferença significativa de outros indicadores pela presença
disso , ou seja, ponteiro para um objeto desta classe. E, por padrão, os ponteiros para um membro da classe têm uma sintaxe separada para definição, são um tipo separado e não podem ser representados por um ponteiro regular. Na prática, isso se traduz no fato de que o tamanho de um ponteiro para um membro da classe geralmente é maior que o tamanho de um ponteiro comum (que
== sizeof (void *) ), porque para implementar funções membro virtuais da classe, bem como armazenar
esse ponteiro, os compiladores geralmente implementam ponteiros para um membro da classe como uma estrutura (leia
sobre funções e
estrutura virtuais). A maneira de apresentar indicadores para os alunos é deixada, de acordo com o padrão, a critério do compilador, mas lembraremos dessa diferença de tamanho e apresentação ao considerar mais códigos.
Para definir um ponteiro regular para um objeto, escreveremos um modelo
is_pointer simples, bem como um modelo
is_lvalue_reference para referências a objetos (separamos
o is_rvalue_reference, porque até o 11º padrão não havia operador
&& , bem como toda a semântica do movimento):
namespace detail { template<class> struct _is_pointer_helper : public false_type { }; template<class _Tp> struct _is_pointer_helper<_Tp*> : public true_type { }; }
Não há mais nada fundamentalmente novo aqui, o mesmo foi feito nas partes anteriores deste capítulo. Vamos continuar definindo ponteiros para objetos - agora vamos olhar para ponteiros para funções.
É importante entender que uma função e uma função membro de uma classe são entidades completamente diferentes de acordo com o padrão:
- O primeiro ponteiro será normal (um ponteiro para um objeto), o segundo terá um ponteiro para um membro da classe.
void (*func_ptr)(int);
- Você pode criar um link para o primeiro (link do objeto), mas não pode criar um segundo link.
void (&func_ref)(int);
Aqui vou mencionar um pouco sobre geração de código. Como antes do C ++ 11 não havia modelos com um número variável de parâmetros, todos os modelos em que poderia haver um número diferente de parâmetros foram
determinados por meio da especialização do modelo principal com um grande número de parâmetros na entrada e sua inicialização pelos parâmetros fictícios padrão. O mesmo se aplica às sobrecargas de funções, como Também não havia macros com um número variável de parâmetros. Como escrever de 60 a 70 linhas do mesmo tipo de especialização de modelos com as mãos, sobrecarregar funções é uma tarefa bastante monótona e inútil, além de estar repleta da possibilidade de cometer um erro.Eu escrevi um gerador simples de código para modelos e sobrecargas de funções para esses fins. Decidi me limitar a definir funções para 24 parâmetros e isso parece bastante complicado no código, mas simples e claro:
namespace detail { template <class R> struct _is_function_ptr_helper : false_type {}; template <class R > struct _is_function_ptr_helper<R(*)()> : true_type {}; template <class R > struct _is_function_ptr_helper<R(*)(...)> : true_type {}; template <class R, class T0> struct _is_function_ptr_helper<R(*)(T0)> : true_type {}; template <class R, class T0> struct _is_function_ptr_helper<R(*)(T0 ...)> : true_type {};
... template <class R, class T0, class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9, class T10, class T11, class T12, class T13, class T14, class T15, class T16, class T17, class T18, class T19, class T20, class T21, class T22, class T23, class T24> struct _is_function_ptr_helper<R(*)(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24)> : true_type {}; template <class R, class T0, class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9, class T10, class T11, class T12, class T13, class T14, class T15, class T16, class T17, class T18, class T19, class T20, class T21, class T22, class T23, class T24> struct _is_function_ptr_helper<R(*)(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24 ...)> : true_type {}; }
Definimos os tipos familiares para nós no capítulo anterior da técnica SFINAE:
namespace detail {
Mais algumas macros para conveniência namespace detail { #define _IS_MEM_FUN_PTR_CLR \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS)); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS...)); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS) const); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS) volatile); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS) const volatile); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS...) const); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS...) volatile); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS...) const volatile); #ifdef _STDEX_CDECL _no_type _STDEX_CDECL _is_mem_function_ptr(...); #define _IS_MEM_FUN_CDECL_PTR \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(__cdecl T::*const volatile*)(ARGS)); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(__cdecl T::*const volatile*)(ARGS) const); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(__cdecl T::*const volatile*)(ARGS) volatile); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(__cdecl T::*const volatile*)(ARGS) const volatile); #define _IS_MEM_FUN_STDCALL_PTR \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(__stdcall T::*const volatile*)(ARGS)); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(__stdcall T::*const volatile*)(ARGS) const); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(__stdcall T::*const volatile*)(ARGS) volatile); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(__stdcall T::*const volatile*)(ARGS) const volatile); #define _IS_MEM_FUN_FASTCALL_PTR \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(__fastcall T::*const volatile*)(ARGS)); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(__fastcall T::*const volatile*)(ARGS) const); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(__fastcall T::*const volatile*)(ARGS) volatile); \ template <class R, class T TYPES > \ _yes_type _is_mem_function_ptr(R(__fastcall T::*const volatile*)(ARGS) const volatile); #else _no_type _is_mem_function_ptr(...); #define _IS_MEM_FUN_CDECL_PTR #define _IS_MEM_FUN_STDCALL_PTR #define _IS_MEM_FUN_FASTCALL_PTR #endif #define _IS_MEM_FUN_PTR \ _IS_MEM_FUN_PTR_CLR \ _IS_MEM_FUN_CDECL_PTR \ _IS_MEM_FUN_STDCALL_PTR \ _IS_MEM_FUN_FASTCALL_PTR }
As macros são definidas para que seja relativamente conveniente redefinir
TYPES e
ARGS define como uma lista de tipos e parâmetros, substituindo a macro
_IS_MEM_FUN_PTR para gerar definições para todos os tipos de funções possíveis pelo pré-processador. Também vale a pena prestar atenção ao fato de que, para os compiladores da Microsoft,
os contratos de chamada (
__fastcall ,
__stdcall e
__cdecl ) também são
importantes , porque com convenções diferentes, as funções serão diferentes, embora tenham o mesmo conjunto de argumentos e valor de retorno. Como resultado, todo esse grandioso design de macro é usado de maneira bastante compacta:
namespace detail { #define TYPES #define ARGS _IS_MEM_FUN_PTR #undef TYPES #undef ARGS #define TYPES , class T0 #define ARGS T0 _IS_MEM_FUN_PTR #undef TYPES #undef ARGS #define TYPES , class T0, class T1 #define ARGS T0, T1 _IS_MEM_FUN_PTR #undef TYPES #undef ARGS
... #define TYPES , class T0, class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9, class T10, class T11, class T12, class T13, class T14, class T15, class T16, class T17, class T18, class T19, class T20, class T21, class T22, class T23, class T24 #define ARGS T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24 _IS_MEM_FUN_PTR #undef TYPES #undef ARGS
E agora, para o que foi tudo escrito:
namespace detail { template <class _Tp, bool _IsRef> struct _is_mem_function_ptr_impl { static _Tp *p; static const bool value = (sizeof(_is_mem_function_ptr(_is_mem_function_ptr_impl::p)) == sizeof(_yes_type)); typedef typename integral_constant<bool, _is_mem_function_ptr_impl::value == bool(true)>::type type; }; template <class _Tp> struct _is_mem_function_ptr_impl<_Tp, true>: public false_type {}; template <class _Tp> struct _is_mem_function_ptr_helper: public _is_mem_function_ptr_impl<_Tp, is_reference<_Tp>::value>::type {}; template <class _Tp, bool _IsMemberFunctionPtr> struct _is_function_chooser_impl : public false_type { }; template <class _Tp> struct _is_function_chooser_impl<_Tp, false> : public _is_function_ptr_helper<_Tp*> { }; template<class _Tp, bool _IsRef = true> struct _is_function_chooser : public false_type { }; template <class _Tp> struct _is_function_chooser<_Tp, false> { static const bool value = _is_function_chooser_impl<_Tp, _is_mem_function_ptr_helper<_Tp>::value>::value; }; }
Para verificar se um tipo é uma função membro de uma classe, primeiro é verificado se o tipo é uma referência. Em seguida, um ponteiro desse tipo é criado e substituído na função de teste. Usando a técnica SFINAE, o compilador seleciona a sobrecarga necessária das funções do probe para esse ponteiro e, com base no resultado da comparação com
_yes_type, forma o resultado.
Com base em uma verificação em uma função membro de uma classe, uma verificação de tipo é escrita por pertencer ao tipo de função. Verificamos se o tipo é referência, caso contrário, procuramos uma especialização adequada de estruturas de análise de modelo para um ponteiro desse tipo, que será
true_type para qualquer ponteiro de função com até 24 parâmetros.
E agora usamos o resultado para implementar
is_function . Aqui, pelo mesmo motivo da
parte anterior , não pude herdar essa estrutura de
integral_constant , portanto o comportamento de
integral_constant é "simulado".
E para implementar
is_member_function_pointer , ainda é mais simples:
Além disso, com base nesses padrões, podemos determinar se o tipo é um membro da classe em princípio:
namespace detail { template<class _Tp> struct _is_member_object_pointer_impl1 : public _not_< _or_<_is_function_ptr_helper<_Tp>, _is_mem_function_ptr_helper<_Tp> > >::type { }; template<class _Tp> struct _is_member_object_pointer_impl2 : public false_type { }; template<class _Tp, class _Cp> struct _is_member_object_pointer_impl2<_Tp _Cp::*> : public true_type { }; template<class _Tp> struct _is_member_object_pointer_helper: public _and_<_is_member_object_pointer_impl1<_Tp>, _is_member_object_pointer_impl2<_Tp> >::type {}; }
Utilizou operações lógicas 'e', 'ou', 'não' em tipos da primeira parte namespace detail { struct void_type {};
Aqui, usamos operações lógicas em tipos que, com a ajuda do modelo
condicional , eventualmente selecionam o tipo de modelo apropriado. Como programação de modelos em toda a sua glória, como resultado, no estágio de compilação, já temos informações sobre se o tipo é membro da classe. Muito "furioso", mas como espetacular e eficaz!
Um pouco de programação de modelos mais pura com os mesmos elementos lógicos e temos
is_fundamental ,
is_compound , etc. sinais (isso me encanta, mas você?):
Um leitor atento perceberá que a definição de is_enum está comentada. O fato é que não encontrei maneiras de distinguir enum de outros tipos, mas acho que isso é viável sem o uso de macros dependentes do compilador. Talvez um leitor atento e experiente lhe diga seu caminho ou linha de pensamento a esse respeito.
Para determinar o fato de que um tipo é uma classe, nada mais é necessário agora
namespace detail { template <class _Tp, bool _IsReference> struct _is_class_helper { typedef integral_constant<bool, false> type; }; template <class _Tp> struct _is_class_helper<_Tp, false> { typedef integral_constant<bool, (is_scalar<_Tp>::value == bool(false))
E tudo ficaria bem, mas a
união em C ++ não pode ser distinguida de uma classe no caso geral. Porque eles são muito semelhantes em suas "manifestações externas" e não pude verificar as diferenças (por exemplo, a incapacidade de herdar da
união ) sem erros de compilação. Talvez alguém lhe diga uma manobra complicada para determinar a
união na compilação, então
is_class corresponderá exatamente ao padrão.
Na
parte final deste capítulo, falarei sobre como
std :: decay e
std :: common_type foram implementados , bem como o que resta a ser adicionado aos
type_traits .
Obrigado pela atenção.