const vs constexpr em variáveis

303

Existe uma diferença entre as seguintes definições?

const     double PI = 3.141592653589793;
constexpr double PI = 3.141592653589793;

Caso contrário, qual estilo é preferido no C ++ 11?

fredoverflow
fonte
2
Superset: stackoverflow.com/questions/14116003/…
Ciro Santilli escreveu:
Ambos são constantes em tempo de compilação. Mas você pode fazer um const_cast do primeiro e escrever nele. Mas será otimizado por qualquer compilador, pois isso não influencia as "leituras", pois elas acontecem no momento da compilação.
Bonita Montero

Respostas:

347

Eu acredito que há uma diferença. Vamos renomeá-los para que possamos falar sobre eles mais facilmente:

const     double PI1 = 3.141592653589793;
constexpr double PI2 = 3.141592653589793;

Ambos PI1e PI2são constantes, o que significa que você não pode modificá-los. No entanto, apenas PI2 é uma constante em tempo de compilação. Ele deve ser inicializado em tempo de compilação. PI1pode ser inicializado em tempo de compilação ou tempo de execução. Além disso, somente PI2 pode ser usado em um contexto que requer uma constante em tempo de compilação. Por exemplo:

constexpr double PI3 = PI1;  // error

mas:

constexpr double PI3 = PI2;  // ok

e:

static_assert(PI1 == 3.141592653589793, "");  // error

mas:

static_assert(PI2 == 3.141592653589793, "");  // ok

Quanto ao que você deve usar? Use o que atenda às suas necessidades. Deseja garantir que você tenha uma constante de tempo de compilação que possa ser usada em contextos em que é necessária uma constante em tempo de compilação? Deseja inicializá-lo com um cálculo feito em tempo de execução? Etc.

Howard Hinnant
fonte
60
Você tem certeza? Porque const int N = 10; char a[N];works e limites da matriz devem ser constantes em tempo de compilação.
Fredoverflow
10
Tenho certeza de que os exemplos que escrevi (testaram cada um deles antes de postar). No entanto, meu compilador me permite converter PI1em uma constante integral em tempo de compilação para uso em uma matriz, mas não como um parâmetro de modelo integral que não seja do tipo. Portanto, a conversibilidade em tempo de compilação PI1para um tipo integral me parece um pouco difícil.
Howard Hinnant
34
@FredOverflow: índices de matriz não const "funcionam" há cerca de uma década (há, por exemplo, uma extensão g ++ para isso), mas isso não significa que seja C ++ estritamente legal (embora alguns padrões C ou C ++ mais recentes tenham tornado legal , eu esqueceu qual). Quanto às diferenças nas constantes de compilação, os parâmetros do modelo e o uso como enuminicializador são as únicas duas diferenças notáveis ​​entre conste constexpr(e nem funcionam de doublequalquer maneira).
11158 Damon
17
O parágrafo 4 de 5.19 Expressões constantes [expr.const] também é uma nota (não normativa) que descreve de maneira famosa que uma implementação pode fazer aritmética de ponto flutuante de maneira diferente (por exemplo, com relação à precisão) no tempo de compilação e no tempo de execução. Portanto, 1 / PI1e 1 / PI2pode produzir resultados diferentes. Eu não acho que esse tecnicismo seja tão importante quanto os conselhos nesta resposta, no entanto.
Luc Danton
4
Mas constexpr double PI3 = PI1;funciona corretamente para mim. (MSVS2013 CTP). O que estou fazendo de errado?
NuPagadi
77

Não há diferença aqui, mas importa quando você tem um tipo que tem um construtor.

struct S {
    constexpr S(int);
};

const S s0(0);
constexpr S s1(1);

s0é uma constante, mas não promete ser inicializado em tempo de compilação. s1é marcado constexpr, portanto é uma constante e, como So construtor também é marcado constexpr, ele será inicializado em tempo de compilação.

Principalmente isso importa quando a inicialização em tempo de execução consumiria muito tempo e você deseja enviar esse trabalho para o compilador, onde também consome tempo, mas não diminui o tempo de execução do programa compilado.

Pete Becker
fonte
3
Concordo: a conclusão a que cheguei foi que constexprlevaria a um diagnóstico caso a computação do objeto em tempo de compilação fosse impossível. O que é menos claro é se uma função que espera um parâmetro constante possa ser executada em tempo de compilação, caso o parâmetro seja declarado como conste não como constexpr: ou seja, seria constexpr int foo(S)executado em tempo de compilação se eu chamar foo(s0)?
Matthieu M.
4
@ MatthieuM: Eu duvido foo(s0)que seja executado em tempo de compilação, mas você nunca sabe: é permitido a um compilador fazer essas otimizações. Certamente, nem gcc 4.7.2 nem clang 3.2 permitir-me para compilarconstexpr a = foo(s0);
rici
50

constexpr indica um valor constante e conhecido durante a compilação.
const indica um valor que é apenas constante; não é obrigatório saber durante a compilação.

int sz;
constexpr auto arraySize1 = sz;    // error! sz's value unknown at compilation
std::array<int, sz> data1;         // error! same problem

constexpr auto arraySize2 = 10;    // fine, 10 is a compile-time constant
std::array<int, arraySize2> data2; // fine, arraySize2 is constexpr

Observe que const não oferece a mesma garantia que constexpr, porque os objetos const não precisam ser inicializados com valores conhecidos durante a compilação.

int sz;
const auto arraySize = sz;       // fine, arraySize is const copy of sz
std::array<int, arraySize> data; // error! arraySize's value unknown at compilation

Todos os objetos constexpr são const, mas nem todos os objetos const são constexpr.

Se você deseja que os compiladores garantam que uma variável tenha um valor que possa ser usado em contextos que exigem constantes em tempo de compilação, a ferramenta a ser alcançada é constexpr, não const.

Ajay yadav
fonte
2
Gostei muito da sua explicação .. você pode comentar mais sobre Onde são os casos em que podemos precisar usar constantes de tempo de compilação em cenários da vida real.
Mayukh Sarkar
1
@MayukhSarkar Simplesmente Google C ++ porque constexpr , por exemplo, stackoverflow.com/questions/4748083/…
underscore_d
18

Uma constante simbólica constexpr deve receber um valor conhecido em tempo de compilação. Por exemplo:

constexpr int max = 100; 
void use(int n)
{
    constexpr int c1 = max+7; // OK: c1 is 107
    constexpr int c2 = n+7;   // Error: we don’t know the value of c2
    // ...
}

Para lidar com casos em que o valor de uma “variável” inicializado com um valor que não é conhecido no momento da compilação, mas nunca muda após a inicialização, o C ++ oferece uma segunda forma de constante (uma const ). Por exemplo:

constexpr int max = 100; 
void use(int n)
{
    constexpr int c1 = max+7; // OK: c1 is 107
    const int c2 = n+7; // OK, but don’t try to change the value of c2
    // ...
    c2 = 7; // error: c2 is a const
}

Tais " variáveis const " são muito comuns por dois motivos:

  1. O C ++ 98 não tinha constexpr; portanto, as pessoas usavam const .
  2. O item de lista “Variáveis” que não são expressões constantes (seu valor não é conhecido no momento da compilação), mas não alteram os valores após a inicialização, eles próprios são amplamente úteis.

Referência: "Programação: princípios e práticas usando C ++" por Stroustrup

Jnana
fonte
25
Talvez você deveria ter mencionado que o texto em sua resposta é tirado diretamente da "Programação: Princípios e Prática Usando C ++" por Stroustrup
Aky