Quando você deve usar o recurso constexpr no C ++ 11?


337

Parece-me que ter uma "função que sempre retorna 5" está quebrando ou diluindo o significado de "chamar uma função". Deve haver um motivo ou uma necessidade desse recurso ou não seria no C ++ 11. Por que está aí?

// preprocessor.
#define MEANING_OF_LIFE 42

// constants:
const int MeaningOfLife = 42;

// constexpr-function:
constexpr int MeaningOfLife () { return 42; }

Parece-me que, se eu escrevesse uma função que retornasse um valor literal e fiz uma revisão de código, alguém me diria que eu deveria declarar um valor constante em vez de escrever o retorno 5.


28
Você pode definir uma função recursiva que retorna a constexpr? Nesse caso, posso ver um uso.
20/01/2019

20
Eu acredito que a pergunta deve indicar "por que introduzir uma nova palavra-chave (!) Se o compilador puder deduzir por si mesmo se uma função pode ser avaliada em tempo de compilação ou não". Tê-lo "garantido por uma palavra-chave" parece bom, mas acho que preferiria ter a garantia sempre que possível, sem a necessidade de uma palavra-chave.
Kos

6
@Kos: Alguém mais familiarizado com os componentes internos do C ++ provavelmente preferiria a sua pergunta, mas a minha pergunta vem da perspectiva de uma pessoa que já escreveu código C antes, mas não conhece as palavras-chave do C ++ 2011, nem os detalhes de implementação do compilador C ++ . Ser capaz de raciocinar sobre otimização de compilador e dedução de expressão constante é um assunto para uma pergunta de usuário mais avançado do que esta.
Warren P

8
@Kos: Eu estava pensando da mesma maneira que você, e a resposta que surgiu foi, sem constexpr, como você (facilmente) saberia que o compilador realmente avaliou a função em tempo de compilação? Suponho que você possa verificar a saída do assembly para ver o que ele fez, mas é mais fácil dizer ao compilador que você precisa dessa otimização e, por algum motivo, não pode fazer isso por você, ele fornecerá uma boa compilação. erro em vez de silenciosamente falhar ao otimizar onde você esperava otimizar.
Jeremy Friesner

3
@ Kos: Você poderia dizer a mesma coisa const. De fato, a intenção obrigatória é útil ! As dimensões da matriz são o exemplo canônico.
Lightness Races em órbita em

Respostas:


303

Suponha que faça algo um pouco mais complicado.

constexpr int MeaningOfLife ( int a, int b ) { return a * b; }

const int meaningOfLife = MeaningOfLife( 6, 7 );

Agora você tem algo que pode ser avaliado em constante, mantendo boa legibilidade e permitindo um processamento um pouco mais complexo do que apenas definir uma constante para um número.

Basicamente, fornece uma boa ajuda para a manutenção, pois fica mais óbvio o que você está fazendo. Tomemos max( a, b )por exemplo:

template< typename Type > constexpr Type max( Type a, Type b ) { return a < b ? b : a; }

É uma escolha bastante simples, mas significa que, se você chamar maxcom valores constantes, é explicitamente calculado em tempo de compilação e não em tempo de execução.

Outro bom exemplo seria uma DegreesToRadiansfunção. Todo mundo acha graus mais fáceis de ler do que radianos. Embora você saiba que 180 graus está em radianos, é muito mais claro escrito da seguinte maneira:

const float oneeighty = DegreesToRadians( 180.0f );

Muitas informações boas aqui:

http://en.cppreference.com/w/cpp/language/constexpr


18
Ponto excelente com ele dizendo ao compilador para tentar calcular o valor em tempo de compilação. Estou curioso por que const não fornece essa funcionalidade quando otimizações específicas são especificadas? Ou faz?
TamusJRoyce

11
@ Tamus: Muitas vezes, mas não é obrigado. O constexpr obriga o compilador e emitirá um erro se não puder.
Goz

20
Eu vejo agora. O pecado (0,5) é outro. Isso substitui as macros C ordenadamente.
Warren P

10
Eu posso ver isso como uma nova pergunta de entrevista: Explique as diferenças entre a palavra-chave const e constexpr.
Warren P

2
Como forma de documentar esse ponto, escrevi código semelhante ao descrito acima e novamente com a função sendo "const" em vez de "constexpr". Como eu estou usando Clang3.3, -pedantic-errors e -std = c ++ 11 eu esperava que o último não fosse compilado. Compilou e executou como no caso "constexpr". Você acha que essa é uma extensão clang ou houve um ajuste nas especificações do C ++ 11 desde que esta postagem foi respondida?
Arbalest

144

Introdução

constexprnão foi introduzido como uma maneira de dizer à implementação que algo pode ser avaliado em um contexto que requer uma expressão constante ; implementações em conformidade conseguiu provar isso antes do C ++ 11.

Algo que uma implementação não pode provar é a intenção de um determinado pedaço de código:

  • O que é que o desenvolvedor deseja expressar com essa entidade?
  • Devemos permitir cegamente que o código seja usado em uma expressão constante , apenas porque funciona?

O que seria do mundo sem constexpr ?

Digamos que você esteja desenvolvendo uma biblioteca e compreenda que deseja calcular a soma de cada número inteiro no intervalo (0,N].

int f (int n) {
  return n > 0 ? n + f (n-1) : n;
}

A falta de intenção

Um compilador pode facilmente provar que a função acima pode ser chamada em uma expressão constante se o argumento passado for conhecido durante a tradução; mas você não declarou isso como uma intenção - aconteceu de acontecer.

Agora alguém aparece, lê sua função, faz a mesma análise que o compilador; " Oh, esta função é utilizável em uma expressão constante!" e escreve o seguinte trecho de código.

T arr[f(10)]; // freakin' magic

A otimização

Você, como um desenvolvedor de biblioteca "impressionante" , decide que fdeve armazenar em cache o resultado ao ser chamado; quem gostaria de calcular o mesmo conjunto de valores repetidamente?

int func (int n) { 
  static std::map<int, int> _cached;

  if (_cached.find (n) == _cached.end ()) 
    _cached[n] = n > 0 ? n + func (n-1) : n;

  return _cached[n];
}

O resultado

Ao introduzir sua otimização boba, você acabou de quebrar todos os usos de sua função que estavam em um contexto em que uma expressão constante era necessária.

Você nunca prometeu que a função era utilizável em uma expressão constante e , sem ela, constexprnão haveria maneira de oferecer tal promessa.


Então, por que precisamos constexpr?

O uso principal do constexpr é declarar intenção .

Se uma entidade não estiver marcada como constexpr- ela nunca foi projetada para ser usada em uma expressão constante ; e, mesmo que seja, contamos com o compilador para diagnosticar esse contexto (porque desconsidera nossa intenção).


25
Essa é provavelmente a resposta correta, uma vez que alterações recentes no C ++ 14 e C ++ 17 permitem que um intervalo muito maior da linguagem seja usado em constexprexpressões. Em outras palavras, praticamente qualquer coisa pode ser anotada constexpr(talvez um dia ela simplesmente desapareça por causa disso?), E a menos que se tenha um critério de quando usar constexprou não, praticamente todo o código será escrito como tal .
alecov 29/07

4
@alecov Definitivamente não tudo ... I/O, syscalle dynamic memory allocationdefinitivamente cann't ser marcado como constexprAlém disso, nem tudo deve ser constexpr.
JiaHao Xu

11
@alecov Algumas funções devem ser executadas em tempo de execução e não fazem sentido em tempo de compilação.
JiaHao Xu

11
Eu também gosto desta resposta da melhor maneira. A avaliação do tempo de compilação é uma ótima otimização, mas o que você realmente obtém constexpré uma garantia de algum tipo de comportamento. Assim como constfaz.
Tomáš Zato - Restabelece Monica

Que compilador permite que essa versão sem custo int f (int n) { return n > 0 ? n + f (n-1) : n;} T arr[f(10)]; não consiga compilar em nenhum lugar?
Jer

91

Tome std::numeric_limits<T>::max(): por qualquer motivo, este é um método. constexprseria benéfico aqui.

Outro exemplo: você deseja declarar uma matriz C (ou a std::array) que é tão grande quanto outra matriz. A maneira de fazer isso no momento é assim:

int x[10];
int y[sizeof x / sizeof x[0]];

Mas não seria melhor ser capaz de escrever:

int y[size_of(x)];

Graças a constexpr, você pode:

template <typename T, size_t N>
constexpr size_t size_of(T (&)[N]) {
    return N;
}

11
+1 para um bom uso do modelo, mas funcionaria exatamente da mesma maneira sem o constexpr, não?
Kos

21
@ Kos: Não. Ele retornaria um valor de tempo de execução. constexprforça o complier a fazer com que a função retorne um valor em tempo de compilação (se possível).
deft_code 20/01

14
@ Kos: sem constexprele, não pode ser usado em uma declaração de tamanho de matriz nem como argumento de modelo, independentemente de o resultado da chamada de função ser uma constante em tempo de compilação ou não. Esses dois são basicamente os únicos casos de uso, constexprmas pelo menos o argumento de modelo é importante.
Konrad Rudolph

2
"por qualquer motivo, este é um método": O motivo é que existem apenas números inteiros de tempo de compilação no C ++ 03, mas não há outros tipos de tempo de compilação, portanto, apenas um método pode funcionar para tipos arbitrários anteriores ao C ++ 11.
Sebastian Mach

5
@LwCui Não, não está "ok": o GCC é apenas negligente por padrão sobre certas coisas. Use a -pedanticopção e ela será sinalizada como um erro.
Konrad Rudolph

19

constexprfunções são realmente legais e um ótimo complemento para o c ++. No entanto, você está certo de que a maioria dos problemas que resolve pode ser deselegantemente contornada com macros.

No entanto, um dos usos de constexprnão possui constantes digitadas equivalentes a C ++ 03.

// This is bad for obvious reasons.
#define ONE 1;

// This works most of the time but isn't fully typed.
enum { TWO = 2 };

// This doesn't compile
enum { pi = 3.1415f };

// This is a file local lvalue masquerading as a global
// rvalue.  It works most of the time.  But May subtly break
// with static initialization order issues, eg pi = 0 for some files.
static const float pi = 3.1415f;

// This is a true constant rvalue
constexpr float pi = 3.1415f;

// Haven't you always wanted to do this?
// constexpr std::string awesome = "oh yeah!!!";
// UPDATE: sadly std::string lacks a constexpr ctor

struct A
{
   static const int four = 4;
   static const int five = 5;
   constexpr int six = 6;
};

int main()
{
   &A::four; // linker error
   &A::six; // compiler error

   // EXTREMELY subtle linker error
   int i = rand()? A::four: A::five;
   // It not safe use static const class variables with the ternary operator!
}

//Adding this to any cpp file would fix the linker error.
//int A::four;
//int A::six;

12
Você poderia esclarecer que "EXTREMELY sutil linker error"? Ou pelo menos fornecer um ponteiro para um esclarecimento?
enobayram

4
@enobayram, O operador ternário pega o endereço dos operandos. Isso não é óbvio no código. Tudo compila bem, mas o link falha porque o endereço de fournão resolve. Eu realmente tive que cavar para descobrir quem estava pegando o endereço da minha static constvariável.
Deft_code 24/07/2013

23
"Isso é ruim por razões óbvias": a razão mais óbvia é o ponto e vírgula, certo?
TonyK

4
O "erro EXTREMELY sutil do vinculador" me deixou completamente intrigado. Nem fournem fiveestão no escopo.
Steven Lu

3
veja também o novo enum classtipo, ele corrige alguns problemas de enumeração.
NinMonkey 5/09/2013

14

Pelo que li, a necessidade de constexpr vem de um problema na metaprogramação. Classes de características podem ter constantes representadas como funções, pense: numeric_limits :: max (). Com constexpr, esses tipos de funções podem ser usados ​​na metaprogramação ou como limites de array, etc.

Outro exemplo de primeira linha seria que, para interfaces de classe, você pode querer que tipos derivados definam suas próprias constantes para alguma operação.

Editar:

Depois de bisbilhotar o SO, parece que outros criaram alguns exemplos do que pode ser possível com o constexprs.


"Para fazer parte de uma interface, você precisa ser uma função"?
Daniel Earwicker

Agora que posso ver a utilidade disso, estou um pouco mais animado com o C ++ 0x. Parece uma coisa bem pensada. Eu sabia que eles deveriam estar. Os uber-geeks padrão da linguagem raramente fazem coisas aleatórias.
Warren P

Estou muito mais empolgado com lambdas, o modelo de encadeamento, initializer_list, referências de rvalue, modelos variados, as novas sobrecargas de ligação ... há muito o que esperar.
luke

11
Ah, sim, mas eu já entendo lambdas / fechamentos em vários outros idiomas. constexpré mais especificamente útil em um compilador com um poderoso sistema de avaliação de expressões em tempo de compilação. C ++ realmente não tem pares nesse domínio. (que é um forte aplauso para C ++ 11, IMHO)
Warren P

11

Do discurso de Stroustrup em "Going Native 2012":

template<int M, int K, int S> struct Unit { // a unit in the MKS system
       enum { m=M, kg=K, s=S };
};

template<typename Unit> // a magnitude with a unit 
struct Value {
       double val;   // the magnitude 
       explicit Value(double d) : val(d) {} // construct a Value from a double 
};

using Speed = Value<Unit<1,0,-1>>;  // meters/second type
using Acceleration = Value<Unit<1,0,-2>>;  // meters/second/second type
using Second = Unit<0,0,1>;  // unit: sec
using Second2 = Unit<0,0,2>; // unit: second*second 

constexpr Value<Second> operator"" s(long double d)
   // a f-p literal suffixed by ‘s’
{
  return Value<Second> (d);  
}   

constexpr Value<Second2> operator"" s2(long double d)
  // a f-p literal  suffixed by ‘s2’ 
{
  return Value<Second2> (d); 
}

Speed sp1 = 100m/9.8s; // very fast for a human 
Speed sp2 = 100m/9.8s2; // error (m/s2 is acceleration)  
Speed sp3 = 100/9.8s; // error (speed is m/s and 100 has no unit) 
Acceleration acc = sp1/0.5s; // too fast for a human

2
Este exemplo também pode ser encontrado no artigo da Stroustrup, Software Development for Infrastructure .
Matthieu Poullet

clang-3.3: error: o tipo de retorno da função constexpr 'Value <Second>' não é um tipo literal
Mitja

Isso é legal, mas quem coloca literais em código como este. Se seu compilador "verificar suas unidades", faria sentido se você estivesse escrevendo uma calculadora interativa.
bobobobo

5
@bobobobo ou se você estivesse escrevendo software de navegação para o Mars Climate Orbiter, talvez :)
Jeremy Friesner

11
Para compilar - 1. Use sublinhado nos sufixos literais. 2. adicione o operador "" _m para 100_m. 3. use 100.0_m ou adicione uma sobrecarga que aceite não assinado por muito tempo. 4. Declare o construtor Value constexpr. 5. Adicione o operador correspondente / à classe Value da seguinte forma: constexpr auto operator / (valor const <Y> & outro) const {return Value <Unit <TheUnit :: m - Value <Y> :: TheUnit :: m, TheUnit :: kg - Valor <Y> :: TheUnit :: kg, TheUnit :: s - Valor <Y> :: TheUnit :: s >> (val / other.val); } Onde TheUnit é typedef para Unit adicionada dentro da classe Value.
0kcats

8

Outro uso (ainda não mencionado) é constexpr construtores. Isso permite criar constantes de tempo de compilação que não precisam ser inicializadas durante o tempo de execução.

const std::complex<double> meaning_of_imagination(0, 42); 

Emparelhe isso com literais definidos pelo usuário e você terá suporte completo para classes literais definidas pelo usuário.

3.14D + 42_i;

6

Costumava haver um padrão com metaprogramação:

template<unsigned T>
struct Fact {
    enum Enum {
        VALUE = Fact<T-1>*T;
    };
};

template<>
struct Fact<1u> {
    enum Enum {
        VALUE = 1;
    };
};

// Fact<10>::VALUE is known be a compile-time constant

Acredito que constexprfoi introduzido para permitir que você escreva essas construções sem a necessidade de modelos e construções estranhas com especialização, SFINAE e outras coisas - mas exatamente como você escreveria uma função de tempo de execução, mas com a garantia de que o resultado será determinado na compilação -Tempo.

No entanto, observe que:

int fact(unsigned n) {
    if (n==1) return 1;
    return fact(n-1)*n;
}

int main() {
    return fact(10);
}

Compile isso g++ -O3e você verá quefact(10) é realmente evado no tempo de compilação!

Um compilador compatível com VLA (portanto, um compilador C no modo C99 ou um compilador C ++ com extensões C99) pode até permitir:

int main() {
    int tab[fact(10)];
    int tab2[std::max(20,30)];
}

Mas, no momento, constexpré C ++ fora do padrão - parece uma maneira de combater isso (mesmo sem o VLA, no caso acima). E ainda há o problema da necessidade de ter expressões constantes "formais" como argumentos de modelo.


A função de fato não é avaliada em tempo de compilação. Ele precisa ser constexpr e deve ter apenas uma declaração de retorno.
Sumant

11
@ Humant: Você está certo que não precisa ser avaliado em tempo de compilação, mas é! Eu estava me referindo ao que realmente acontece nos compiladores. Compile no GCC recente, veja asm resultante e verifique você mesmo se não acredita em mim!
19711 Kos

Tente adicionar std::array<int, fact(2)>e você verá que fato () não é avaliado em tempo de compilação. É apenas o otimizador do GCC fazendo um bom trabalho.

11
Foi o que eu disse ... não sou realmente claro? Veja o último parágrafo
Kos

5

Acabei de começar a mudar o projeto para o c ++ 11 e me deparei com uma situação perfeitamente boa para o constexpr, que limpa métodos alternativos de executar a mesma operação. O ponto principal aqui é que você só pode colocar a função na declaração de tamanho da matriz quando ela for declarada constexpr. Há várias situações em que vejo isso sendo muito útil para avançar com a área de código em que estou envolvido.

constexpr size_t GetMaxIPV4StringLength()
{
    return ( sizeof( "255.255.255.255" ) );
}

void SomeIPFunction()
{
    char szIPAddress[ GetMaxIPV4StringLength() ];
    SomeIPGetFunction( szIPAddress );
}

4
Isso também pode ser escrito: const size_t MaxIPV4StringLength = sizeof ("255.255.255.255");
Superfly Jon

static inline constexpr const autoprovavelmente é melhor.
JiaHao Xu

3

Todas as outras respostas são ótimas, só quero dar um exemplo legal de uma coisa que você pode fazer com o constexpr que é incrível. O See-Phit ( https://github.com/rep-movsd/see-phit/blob/master/seephit.h ) é um analisador de HTML em tempo de compilação e um mecanismo de modelo. Isso significa que você pode inserir HTML e sair de uma árvore que possa ser manipulada. Fazer a análise em tempo de compilação pode oferecer um desempenho extra.

No exemplo da página do github:

#include <iostream>
#include "seephit.h"
using namespace std;



int main()
{
  constexpr auto parser =
    R"*(
    <span >
    <p  color="red" height='10' >{{name}} is a {{profession}} in {{city}}</p  >
    </span>
    )*"_html;

  spt::tree spt_tree(parser);

  spt::template_dict dct;
  dct["name"] = "Mary";
  dct["profession"] = "doctor";
  dct["city"] = "London";

  spt_tree.root.render(cerr, dct);
  cerr << endl;

  dct["city"] = "New York";
  dct["name"] = "John";
  dct["profession"] = "janitor";

  spt_tree.root.render(cerr, dct);
  cerr << endl;
}

1

Seu exemplo básico serve ao mesmo argumento que o das constantes. Por que usar

static const int x = 5;
int arr[x];

sobre

int arr[5];

Porque é muito mais sustentável. O uso do constexpr é muito, muito mais rápido de escrever e ler do que as técnicas de metaprogramação existentes.


0

Pode ativar algumas novas otimizações. consttradicionalmente é uma dica para o sistema de tipos e não pode ser usado para otimização (por exemplo, uma constfunção membro pode const_caste modifica o objeto de qualquer maneira, legalmente, portanto constnão pode ser confiável para otimização).

constexprsignifica que a expressão é realmente constante, desde que as entradas para a função sejam const. Considerar:

class MyInterface {
public:
    int GetNumber() const = 0;
};

Se isso for exposto em algum outro módulo, o compilador não pode confiar que GetNumber()não retornará valores diferentes cada vez que for chamado - mesmo consecutivamente sem chamadas não-const no meio - porqueconst poderia ter sido descartado na implementação. (Obviamente, qualquer programador que fez isso deve ser filmado, mas a linguagem permite, portanto, o compilador deve cumprir as regras.)

Adicionando constexpr:

class MyInterface {
public:
    constexpr int GetNumber() const = 0;
};

O compilador agora pode aplicar uma otimização na qual o valor de retorno GetNumber()é armazenado em cache e eliminar chamadas adicionais para GetNumber(), porque constexpré uma garantia mais forte de que o valor de retorno não será alterado.


Na verdade, const pode ser usado na otimização ... É um comportamento indefinido modificar um valor definido const mesmo depois de um const_castIIRC. Eu esperava que fosse consistente para as constfunções de membro, mas precisaria verificar isso com o padrão. Isso significa que o compilador pode fazer otimizações com segurança lá.
Kos

11
@ Warren: não importa se a otimização é realmente feita, é apenas permitido. @ Kos: é uma sutileza pouco conhecida que, se o objeto original não foi declarado const ( int xvs. const int x), é seguro modificá-lo const_castafastando const em um ponteiro / referência a ele. Caso contrário, const_castsempre chamaria um comportamento indefinido e seria inútil :) Nesse caso, o compilador não possui informações sobre a consistência do objeto original, portanto não pode dizer.
precisa saber é o seguinte

@ Kos Eu não acho que const_cast é o único problema aqui. O método const tem permissão para ler e até modificar uma variável global. Por outro lado, alguém do thread anpther também pode modificar o objeto const entre as chamadas.
22413 enobayram

11
O "= 0" não é válido aqui e deve ser removido. Eu faria isso sozinho, mas não tenho certeza de que esteja em conformidade com o protocolo SO.
precisa saber é o seguinte

Ambos os exemplos são inválidos: o primeiro ( int GetNumber() const = 0;) deve declarar o GetNumber()método virtual. O segundo ( constexpr int GetNumber() const = 0;) não é válido porque o especificador puro ( = 0) implica que o método seja virtual, mas os constexpr não devem ser virtuais (ref: en.cppreference.com/w/cpp/language/constexpr )
stj

-1

Quando usar constexpr:

  1. sempre que houver uma constante de tempo de compilação.

Embora eu concorde com você, esta resposta não explica por que constexpr deve ser preferida às macros do pré-processador ou const.
Sneftel

-3

É útil para algo como

// constants:
const int MeaningOfLife = 42;

// constexpr-function:
constexpr int MeaningOfLife () { return 42; }

int some_arr[MeaningOfLife()];

Amarre isso com uma classe de características ou algo semelhante e isso se torna bastante útil.


4
No seu exemplo, ele oferece vantagem zero sobre uma constante simples, portanto não responde realmente à pergunta.
jalf

Este é um exemplo artificial, imagine se o MeaningOfLife () obtiver seu valor de outro lugar, digamos outra função ou um #define ou uma série dele. Você pode não saber o que retorna, pode ser o código da biblioteca. Outros exemplos, imagine um contêiner imutável que tenha um método constexpr size (). Agora você pode fazer int arr [container.size ()];
plivesey

2
@plivesey você pode editar sua resposta com um exemplo melhor então.
Mukesh
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.