Por que não posso ter um membro const estático não integral em uma class?

Eu notei que o C ++ não irá compilar o seguinte:

class No_Good { static double const d = 1.0; }; 

No entanto, permitirá alegremente uma variação em que o double é alterado para um tipo int, unsigned ou qualquer tipo integral:

 class Happy_Times { static unsigned const u = 1; }; 

Minha solução foi alterá-lo para ler:

 class Now_Good { static double d() { return 1.0; } }; 

e descobrir que o compilador será inteligente o suficiente para inline quando necessário … mas isso me deixou curioso.

Por que o (s) designer (es) C ++ me permitiria a static static um int ou unsigned, mas não um double?

Edit: Eu estou usando visual studio 7.1 (.net 2003) no Windows XP.

Edit2:

A pergunta foi respondida, mas para conclusão, o erro que eu estava vendo:

 error C2864: 'd' : only const static integral data members can be initialized inside a class or struct 

O problema é que, com um inteiro, o compilador geralmente não precisa criar um endereço de memory para a constante. Ele não existe em tempo de execução e todo uso dele é embutido no código circundante. Ele ainda pode decidir dar a ele um local de memory – se o endereço dele for sempre tomado (ou se for passado por uma referência const a uma function), isso deve acontecer. Para fornecer um endereço, ele precisa ser definido em alguma unidade de tradução. E, nesse caso, você precisa separar a declaração da definição, pois, caso contrário, ela seria definida em várias unidades de tradução.

Usando g ++ sem otimização ( -O0 ), ele automaticamente incorpora variables ​​inteiras constantes, mas não valores duplos constantes. Em níveis de otimização mais altos (por exemplo, -O1 ), ele insere duplas constantes. Assim, o código a seguir compila em -O1 mas NÃO em -O0 :

 // File ah class X { public: static const double d = 1.0; }; void foo(void); // File a.cc #include  #include "ah" int main(void) { foo(); printf("%g\n", X::d); return 0; } // File b.cc #include  #include "ah" void foo(void) { printf("foo: %g\n", X::d); } 

Linha de comando:

 g++ a.cc b.cc -O0 -oa # Linker error: ld: undefined symbols: X::d g++ a.cc b.cc -O1 -oa # Succeeds 

Para máxima portabilidade, você deve declarar suas constantes nos arquivos de header e defini-los uma vez em algum arquivo de origem. Sem otimização, isso não prejudicará o desempenho, pois você não está otimizando de qualquer maneira, mas com as otimizações ativadas, isso pode prejudicar o desempenho, pois o compilador não pode mais inserir essas constantes em outros arquivos de origem, a menos que você ative a “otimização total do programa” .

Não vejo razão técnica porque

 struct type { static const double value = 3.14; }; 

é proibido. Qualquer ocasião em que você encontrar o local de trabalho é devido a resources definidos de implementação não portáteis. Eles também parecem ser de uso limitado. Para constantes integrais inicializadas em definições de class, você pode usá-las e passá-las para modelos como argumentos não-tipo e usá-las como o tamanho das dimensões da matriz. Mas você não pode fazer isso por constantes de ponto flutuante. Permitir parâmetros de modelo de ponto flutuante traria seu próprio conjunto de regras que realmente não valem a pena.

No entanto, a próxima versão em C ++ permitirá que usando constexpr :

 struct type { static constexpr double value = 3.14; static constexpr double value_as_function() { return 3.14; } }; 

E fará com que type::value uma expressão constante. Enquanto isso, sua melhor aposta é seguir o padrão também usado por std::numeric_limits :

 struct type { static double value() { return 3.14; } }; 

Ele não retornará uma expressão constante (o valor não é conhecido em tempo de compilation), mas isso só importa teórico, já que o valor será embutido de qualquer maneira. Veja a proposta constexpr . Contém

4,4

Floating-point constant expressions

Tradicionalmente, a avaliação da expressão constante de ponto flutuante em tempo de compilation é um assunto espinhoso. Para uniformidade e generalidade, sugerimos permitir dados de expressão constante de tipos de pontos flutuantes, inicializados com qualquer expressão constante de ponto flutuante. Isso também aumentará a compatibilidade com o C99 [ISO99, §6.6], que permite

[# 5] Uma expressão que avalia uma constante é necessária em vários contextos. Se uma expressão flutuante for avaliada no ambiente de tradução, a precisão aritmética e a faixa devem ser pelo menos tão grandes quanto se a expressão estivesse sendo avaliada no ambiente de execução.

Ele realmente não fornece uma justificativa, mas eis o que a Stroustrup tem a dizer sobre isso em “A Terceira Edição da Linguagem de Programação C ++”:

10.4.6.2 Constantes dos membros

Também é possível inicializar um membro constante integral estático adicionando um inicializador de expressão constante à sua declaração de membro. Por exemplo:

 class Curious { static const int c1 = 7; // ok, but remember definition static int c2 = 11; // error: not const const int c3 = 13; // error: not static static const int c4 = f(17); // error: in-class initializer not constant static const float c5 = 7.0; // error: in-class not integral // ... }; 

No entanto, um membro inicializado ainda deve ser (exclusivamente) definido em algum lugar e o inicializador não pode ser repetido:

 const int Curious::c1; // necessary, but don't repeat initializer here 

Eu considero isso uma falha. Quando você precisar de uma constante simbólica em uma declaração de class, use um enumerador (4.8, 14.4.6, 15.3). Por exemplo:

 class X { enum { c1 = 7, c2 = 11, c3 = 13, c4 = 17 }; // ... }; 

Dessa forma, nenhuma definição de membro é necessária em outro lugar, e você não é tentado a declarar variables, números de ponto flutuante, etc.

E no Apêndice C (Technicalities) na Seção C.5 (Expressões Constantes), Stroustrup tem a dizer sobre “expressões constantes”:

Em locais como limites de matriz (5.2), labels de caso (6.3.2) e inicializadores para enumeradores (4.8), o C ++ requer uma expressão constante . Uma expressão constante é avaliada como uma constante integral ou de enumeração. Tal expressão é composta de literais (4.3.1, 4.4.1, 4.5.1), enumeradores (4.8) e consts inicializados por expressões constantes. Em um modelo, um parâmetro de modelo inteiro também pode ser usado (C.13.3). Literais flutuantes (4.5.1) só podem ser usados ​​se explicitamente convertidos em um tipo integral. Funções, objects de class, pointers e referências podem ser usados ​​como operandos somente para o operador sizeof (6.2).

Intuitivamente, as expressões constantes são expressões simples que podem ser avaliadas pelo compilador antes que o programa seja vinculado (9.1) e comece a ser executado.

Note que ele praticamente deixa de lado o ponto flutuante como sendo capaz de tocar em ‘expressões constantes’. Eu suspeito que o ponto flutuante foi deixado de fora desses tipos de expressões constantes simplesmente porque eles não são “simples” o suficiente.

Eu não sei porque trataria um duplo diferente de um int. Eu pensei que tinha usado essa forma antes. Aqui está uma solução alternativa:

 class Now_Better { static double const d; }; 

E no seu arquivo .cpp:

 double const Now_Better::d = 1.0; 

aqui está o meu entendimento baseado na declaração de Stroustrup sobre definição em class

Uma class é geralmente declarada em um arquivo de header e um arquivo de header é normalmente incluído em várias unidades de tradução. No entanto, para evitar regras de vinculador complicadas, o C ++ exige que cada object tenha uma definição exclusiva. Essa regra seria quebrada se o C ++ permitisse a definição em class de entidades que precisavam ser armazenadas na memory como objects.

http://www.stroustrup.com/bs_faq2.html#in-class

Então, basicamente, isso não é permitido porque o C ++ não permite isso. Para tornar as regras do vinculador mais simples, o C ++ requer que cada object tenha uma definição exclusiva.

O membro estático possui apenas uma instância no escopo da class, não como variables ​​estáticas regulares usadas intensamente em C, que possui apenas uma insta- lação dentro de uma unidade de tradução.

Se membro estático é definido em class, e a definição de class será incluída em muitas unidades de tradução, para que o vinculador tenha que fazer mais trabalho para decidir qual membro estático deve ser usado como o único através de toda a unidade de tradução relacionada.

Mas para variables ​​estáticas regulares, elas só podem ser usadas dentro de uma unidade de tradução, mesmo no caso de variables ​​estáticas diferentes em diferentes unidades de tradução com o mesmo nome, elas não afetarão umas às outras. O Linker pode fazer um trabalho simples para vincular variables ​​estáticas regulares dentro de uma unidade de tradução.

para diminuir as complicações e fornecer a function de base, o C ++ fornece a única definição em class para uma constante estática do tipo integral ou de enumeração.