
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". Na primeira parte, examinamos minha implementação dos modelos mais simples da biblioteca padrão, mas agora vamos nos aprofundar nos modelos.
Link para o GitHub com o resultado de hoje para impacientes e não leitores:
Compromissos e críticas construtivas são bem-vindos
Continuação da imersão no mundo do "template magic" C ++.
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.2 Sobre quantos erros milagrosos o log compila
Na
primeira parte deste capítulo, os modelos básicos
type_traits foram introduzidos, mas faltavam mais alguns para o conjunto completo.
Por exemplo, os modelos
is_integral e
is_floating_point eram simplesmente necessários, os quais são realmente muito trivialmente definidos - por meio da especialização de modelo para cada tipo
interno . A questão aqui surgiu apenas com os tipos "grandes" de
longos . O fato é que esse tipo, como incorporado, aparece no padrão da linguagem C ++ somente da versão 11. E seria lógico supor que tudo se resume a verificar a versão do padrão C ++ (que
é difícil de determinar de qualquer maneira ), mas não estava lá.

Como, desde 1999, existe o padrão da linguagem C99 C no qual os tipos
long long int e
long long int já estavam presentes (desde 1999!), E desde que a linguagem C ++ procurou manter a compatibilidade com versões anteriores do C puro, muitos compiladores (que C / C ++ geralmente misto) acabou de adicioná-lo como um tipo fundamental antes mesmo do lançamento do padrão C ++ 03. Ou seja, a situação era que o tipo interno é de fato (de C), mas não é descrito no padrão C ++ e não deveria estar lá. E isso adiciona um pouco mais de confusão à implementação da biblioteca padrão. Mas vamos dar uma olhada no código:
namespace detail { template <class> struct _is_floating_point : public false_type {}; template<> struct _is_floating_point<float> : public true_type {}; template<> struct _is_floating_point<double> : public true_type {}; template<> struct _is_floating_point<long double> : public true_type {}; } template <class _Tp> struct is_floating_point : public detail::_is_floating_point<typename remove_cv<_Tp>::type> { };
Tudo fica claro com o código acima - nós especializamos o modelo para os tipos de ponto flutuante necessários e, depois de "limpar" os modificadores de tipo, dizemos "sim" ou "não" ao tipo que nos foi passado. Os próximos na linha são os tipos inteiros:
namespace detail { template <class> struct _is_integral_impl : public false_type {}; template<> struct _is_integral_impl<bool> : public true_type {}; template<> struct _is_integral_impl<char> : public true_type {}; template<> struct _is_integral_impl<wchar_t> : public true_type {}; template<> struct _is_integral_impl<unsigned char> : public true_type {}; template<> struct _is_integral_impl<unsigned short int> : public true_type {}; template<> struct _is_integral_impl<unsigned int> : public true_type {}; template<> struct _is_integral_impl<unsigned long int> : public true_type {}; #ifdef LLONG_MAX template<> struct _is_integral_impl<unsigned long long int> : public true_type {}; #endif template<> struct _is_integral_impl<signed char> : public true_type {}; template<> struct _is_integral_impl<short int> : public true_type {}; template<> struct _is_integral_impl<int> : public true_type {}; template<> struct _is_integral_impl<long int> : public true_type {}; #ifdef LLONG_MAX template<> struct _is_integral_impl<long long int> : public true_type {}; #endif template <class _Tp> struct _is_integral : public _is_integral_impl<_Tp> {}; template<> struct _is_integral<char16_t> : public true_type {}; template<> struct _is_integral<char32_t> : public true_type {}; template<> struct _is_integral<int64_t> : public true_type {}; template<> struct _is_integral<uint64_t> : public true_type {}; } template <class _Tp> struct is_integral : public detail::_is_integral<typename remove_cv<_Tp>::type> { };
Aqui você precisa parar um pouco e pensar. Para tipos inteiros "antigos" como
int ,
bool etc. fazemos as mesmas especializações que com
is_floating_point . Para os "novos" tipos
long long int e sua contraparte não assinada, definimos sobrecargas apenas se houver uma
definição LLONG_MAX, definida em C ++ 11 (como o primeiro padrão C ++ compatível com C99) e deve ser definida no arquivo de cabeçalho
climits como máximo um grande número que se encaixa em um objeto do tipo
long long int .
Climits também tem mais algumas definições de macro (para o menor número possível e equivalentes não assinados), mas eu decidi usar essa macro, o que não é importante. O importante é que, diferentemente do boost, nesta implementação os tipos "grandes" de C não serão definidos como constantes inteiras, embora estejam (possivelmente) presentes no compilador. O mais importante são os tipos
char16_t e
char32_t , que também foram introduzidos no C ++ 11, mas ainda não foram entregues no C99 (eles já apareceram simultaneamente com o C ++ no padrão C11) e, portanto, nos padrões antigos, sua definição pode apenas através de um alias de tipo (por exemplo,
typedef short char16_t , mas mais sobre isso mais tarde). Nesse caso, para que a especialização do modelo lide corretamente com situações em que esses tipos são separados (integrados) e quando são definidos por meio de
typedef ,
é necessária mais uma camada de
detalhe da especialização do modelo
:: _ is_integral .
Um fato interessante é que em alguns compiladores antigos esses tipos "grandes" C-tímidos não são constantes integrais . O que pode ser entendido e até perdoado, já que esses tipos não são padrão para C ++ até 11 padrões e, em geral, não devem estar lá. Mas o que é difícil de entender é que esses tipos no compilador C ++ mais recente da criatividade Embarcadero (Embarcadero C ++ Builder), que o C ++ 11 supostamente suporta, ainda não são constantes constantes em seus assemblies de 32 bits (há 20 anos atrás) então era a Borland ainda verdadeira). Aparentemente, por causa disso, inclusive, a maioria da biblioteca C ++ 11 padrão está ausente nesses assemblies de 32 bits (#include ratio? Chrono? Vai custar). A Embarcadero parece ter decidido forçar a era de 64 bits com o lema: “Você quer C ++ 11 ou um padrão mais novo? Crie um programa de 64 bits (e apenas clang, nosso compilador não pode)! ”
Após finalizar o processo com os tipos fundamentais de linguagem, apresentamos alguns padrões mais simples:
Padrões simples template <bool, class _Tp = detail::void_type> struct enable_if { }; template <class _Tp> struct enable_if<true, _Tp> { typedef _Tp type; }; template<class, class> struct is_same : public false_type { }; template<class _Tp> struct is_same<_Tp, _Tp> : public true_type
Apenas o fato de os modelos serem especializados para todos os modificadores do tipo (
volátil e
constante volátil, por exemplo) é digno de nota aqui, porque alguns compiladores tendem a "perder" um dos modificadores ao expandir o modelo.
Separadamente, destaquei a implementação de
is_signed e
is_unsigned :
namespace detail { template<bool> struct _sign_unsign_chooser; template<class _Tp> struct _signed_comparer { static const bool value = _Tp(-1) < _Tp(0); }; template<class _Tp> struct _unsigned_comparer { static const bool value = _Tp(0) < _Tp(-1); }; template<bool Val> struct _cat_base : integral_constant<bool, Val> {
Ao implementar esta parte, entrei em uma batalha desigual com o Borland C ++ Builder 6.0, que não queria tornar esses dois modelos herdados do
integral_constant , o que acabou resultando em dezenas de erros internos do compilador "imitando" o comportamento
integral_constant desses modelos. Aqui, talvez, valha a pena ainda lutar e apresentar algum tipo de derivação complicada do tipo
is_ * un *: integral_constant através de modelos, mas até agora adiei essa tarefa como não prioritária. O que é interessante na seção de código acima é como, em tempo de compilação, é determinado que o tipo não está assinado / assinado. Para começar, todos os tipos não inteiros são
marcados e, para eles, o modelo vai para um ramo especializado separado
_sign_unsign_chooser com o argumento do modelo
false , que por sua vez sempre retorna
valor == false para qualquer tipo, exceto os tipos de ponto flutuante padrão (eles sempre são assinados por motivos óbvios, assim
_signed :: value será
verdadeiro ). Para tipos inteiros, são realizadas verificações simples, mas divertidas. Aqui usamos o fato de que, para tipos inteiros não assinados, quando o número diminui e passa através de um mínimo (0 obviamente), ocorre um estouro e o número adquire o seu valor máximo possível.
Esse fato é bem conhecido, bem como o fato de que para tipos assinados o estouro
é um comportamento indefinido e você precisa observá-lo (de acordo com o padrão, não é possível reduzir uma variável
int menor que
INT_MIN e espero que, como resultado do estouro, você tenha
INT_MAX , não 42 ou um disco rígido formatado )
Nós escrevemos
_Tp (-1) <_Tp (0) para verificar o tipo de "sinal" usando esse fato; depois, para os tipos não assinados -1, "transforma" através do estouro para o número máximo desse tipo, enquanto para tipos assinados essa comparação será realizada sem estouro, e -1 serão comparados com 0.
E o último de hoje, mas longe do último "truque" da minha biblioteca, é a implementação do
alignment_of :
namespace detail { template <class _Tp> struct _alignment_of_trick { char c; _Tp t; _alignment_of_trick(); }; template <unsigned A, unsigned S> struct _alignment_logic_helper { static const std::size_t value = A < S ? A : S; }; template <unsigned A> struct _alignment_logic_helper<A, 0> { static const std::size_t value = A; }; template <unsigned S> struct _alignment_logic_helper<0, S> { static const std::size_t value = S; }; template< class _Tp > struct _alignment_of_impl { #if _MSC_VER > 1400
A Microsoft novamente se destacou aqui com seu Visual Studio, que, mesmo tendo uma macro interna __alignof interna , ainda produz resultados incorretos ao usá-la.
Explicação do impulsoOs usuários do Visual C ++ devem observar que o MSVC possui diferentes definições de "alinhamento". Por exemplo, considere o seguinte código:
typedef long long align_t; assert(boost::alignment_of<align_t>::value % 8 == 0); align_t a; assert(((std::uintptr_t)&a % 8) == 0); char c = 0; align_t a1; assert(((std::uintptr_t)&a1 % 8) == 0);
Nesse código, mesmo que o boost :: alignment_of <align_t> relate que o align_t possui um alinhamento de 8 bytes, a declaração final falhará em uma compilação de 32 bits porque a1 não está alinhado em um limite de 8 bytes. Observe que, se tivéssemos usado o __alignof intrínseco do MSVC no lugar de boost :: alignment_of, ainda obteríamos o mesmo resultado. De fato, os requisitos (e promessas) de alinhamento do MSVC realmente se aplicam apenas ao armazenamento dinâmico, e não à pilha.
Deixe-me lembrá-lo do que o modelo
std :: alignment_of deve fazer - retornar um valor que represente os requisitos para a colocação de um elemento desse tipo na memória. Um pouco de distração, então um elemento de cada tipo tem algum tipo de alocação de memória e, se for contínuo para uma matriz de elementos, por exemplo, as classes podem ter "buracos" entre os elementos membros da classe (
sizeof class
struct { char a;} provavelmente não será igual a 1, embora exista 1 byte de tudo dentro, porque o compilador o alinhará a 1 + 3 bytes durante o processo de otimização).
Agora vamos olhar o código novamente. Declaramos a estrutura
_alignment_of_trick , na qual colocamos um elemento do tipo que está sendo verificado com um "recuo" na memória de 1 byte. E verifique o alinhamento, subtraindo simplesmente o tamanho do tipo que está sendo verificado do tamanho da estrutura resultante. Ou seja, se o compilador decidir "colar" um espaço vazio entre o elemento do tipo que está sendo verificado e o
caractere anterior, obteremos o valor de alinhamento do tipo na estrutura.
Também aqui a afirmação estática é encontrada pela primeira vez como um tipo. Eles são declarados como:
namespace intern {
De fato, esses modelos especializados são necessários para substituir o
static_assert do C ++ 11, localizado dentro da definição de classe. Essa afirmação é mais leve e altamente especializada que a implementação geral de
STATIC_ASSERT do
capítulo 2 e permite que você não arraste o arquivo de cabeçalho
core.h para
type_traits .

Muitos padrões? Haverá mais! Por enquanto, continuaremos com isso, pois a história fascinante sobre a combinação de programação de modelos com a tecnologia SFINAE, bem como o motivo pelo qual tive que escrever um pequeno gerador de código, continuarão.
Obrigado pela atenção.