Referência indefinida ao estático constexpr char []

186

Eu quero ter uma static const charmatriz na minha classe. O GCC reclamou e me disse que eu deveria usar constexpr, embora agora esteja me dizendo que é uma referência indefinida. Se eu fizer da matriz um não membro, ela será compilada. O que está acontecendo?

// .hpp
struct foo {
  void bar();
  static constexpr char baz[] = "quz";
};

// .cpp
void foo::bar() {
  std::string str(baz); // undefined reference to baz
}
Pubby
fonte
1
Apenas um palpite, funciona se baz é int, por exemplo? Você pode acessá-lo? Também pode ser um bug.
precisa saber é o seguinte
1
@Pubby: Pergunta: Em que unidade de tradução será definida? Resposta: Tudo o que inclui o cabeçalho. Problema: viola a regra de uma definição. Exceção: integrais constantes em tempo de compilação podem ser "inicializadas" nos cabeçalhos.
Mooing Duck 4/11/11
Compila bem como um int@MooingDuck Funciona bem como um não membro. Isso não violaria a regra também?
precisa saber é o seguinte
@ Pubby8: intfraude s. Como um terceiro, que não deve ser permitido, a menos que as regras mudado para C ++ 11 (possível)
Mooing pato
Considerando as opiniões e votos positivos, essa pergunta exigiu uma resposta mais detalhada, que eu adicionei abaixo.
Shafik Yaghmour 4/03/15

Respostas:

188

Adicione ao seu arquivo cpp:

constexpr char foo::baz[];

Razão: Você deve fornecer a definição do membro estático, bem como a declaração. A declaração e o inicializador vão para dentro da definição de classe, mas a definição de membro deve ser separada.

Kerrek SB
fonte
70
Isso parece estranho ... uma vez que não parecem fornecer compilador com alguma informação que ele não tinha antes ...
videiras
32
Parece ainda mais estranho quando você tem sua declaração de classe no arquivo .cpp! Você inicializa o campo na declaração de classe, mas ainda precisa " declarar " o campo escrevendo constexpr char foo :: baz [] abaixo da classe. Parece que os programadores que usam constexpr podem compilar seus programas seguindo uma dica estranha: declare-o novamente.
Lukasz Czerwinski 19/03/14
5
@LukaszCzerwinski: A palavra que você está procurando é "define".
21414 Kerrek SB
5
Certo, não há novas informações: declare usandodecltype(foo::baz) constexpr foo::baz;
não usuário
6
como será a expressão se foo for modelado? obrigado.
Hei
80

C ++ 17 apresenta variáveis ​​embutidas

O C ++ 17 corrige esse problema para constexpr staticvariáveis ​​de membro que requerem uma definição fora de linha se ela foi usada com odr. Veja a segunda metade desta resposta para obter detalhes pré-C ++ 17.

A proposta P0386 Variáveis ​​em linha apresenta a capacidade de aplicar o inlineespecificador às variáveis. Em particular neste caso, constexprimplica inlineem variáveis ​​de membro estáticas. A proposta diz:

O especificador em linha pode ser aplicado a variáveis ​​e também a funções. Uma variável declarada em linha tem a mesma semântica que uma função declarada em linha: ela pode ser definida de forma idêntica em várias unidades de tradução, deve ser definida em todas as unidades de tradução em que é usada pelo odr, e o comportamento do programa é como se existe exatamente uma variável.

e modificado [basic.def] p2:

Uma declaração é uma definição, a menos que
...

  • declara um membro de dados estático fora de uma definição de classe e a variável foi definida dentro da classe com o especificador constexpr (esse uso foi descontinuado; consulte [depr.static_constexpr]),

...

e adicione [depr.static_constexpr] :

Para compatibilidade com as normas internacionais C ++ anteriores, um membro de dados estáticos constexpr pode ser redundantemente redeclarado fora da classe sem inicializador. Este uso está obsoleto. [Exemplo:

struct A {
  static constexpr int n = 5;  // definition (declaration in C++ 2014)
};

constexpr int A::n;  // redundant declaration (definition in C++ 2014)

 - exemplo final]


C ++ 14 e versões anteriores

No C ++ 03, é permitido fornecer inicializadores em classe apenas para integrais const ou tipos de enumeração const ; no C ++ 11, constexpresse recurso foi estendido para tipos literais .

No C ++ 11, não precisamos fornecer uma definição de escopo de espaço para nome para um constexprmembro estático, se não for usado por odr , podemos ver isso na seção padrão do C ++ 11 9.4.2 preliminar [class.static.data], que diz ( ênfase minha daqui para frente ):

[...] Um membro de dados estáticos do tipo literal pode ser declarado na definição de classe com o especificador constexpr; nesse caso, sua declaração deve especificar um colchete ou inicializador igual no qual toda cláusula de inicializador que é uma expressão de atribuição é uma expressão constante. [Nota: Nos dois casos, o membro pode aparecer em expressões constantes. - end note] O membro ainda deve ser definido no escopo do espaço para nome, se for usado por odr (3.2) no programa e a definição do escopo do espaço para nome não deve conter um inicializador.

Então a pergunta se torna, é baz usada aqui:

std::string str(baz); 

e a resposta é sim ; portanto, também precisamos de uma definição de escopo de espaço para nome.

Então, como determinamos se uma variável é usada por odr ? O texto original do C ++ 11 na seção 3.2 [basic.def.odr] diz:

Uma expressão é potencialmente avaliada, a menos que seja um operando não avaliado (Cláusula 5) ou uma subexpressão dela. Uma variável cujo nome aparece como uma expressão potencialmente avaliada é usada como odr, a menos que seja um objeto que satisfaça os requisitos para aparecer em uma expressão constante (5.19) e a conversão de valor em valor (4.1) seja aplicada imediatamente .

O bazmesmo produz uma expressão constante, mas a conversão lvalue em rvalue não é aplicada imediatamente, pois não é aplicável por bazser uma matriz. Isso é abordado na seção 4.1 [conv.lval], que diz:

Um valor glvalue (3.10) de um tipo T sem função e sem matriz pode ser convertido em um valor preterido.53 [...]

O que é aplicado na conversão de matriz em ponteiro .

Essa redação de [basic.def.odr] foi alterada devido ao Relatório de Defeitos 712, pois alguns casos não foram cobertos por essa redação, mas essas alterações não alteram os resultados desse caso.

Shafik Yaghmour
fonte
Então, estamos claros de que não constexprtem absolutamente nada a ver com isso? ( bazÉ uma expressão de qualquer maneira constante)
MM
O @MattMcNabb bem constexpr será necessário se o membro não for um, integral or enumeration typemas caso contrário, sim, o que importa é que seja uma expressão constante .
Shafik Yaghmour
No primeiro parágrafo do "ord-usado" deve ler-se "ODR-usado", creio eu, mas eu nunca estou certo com C ++
Egor Pasko
37

Isso é realmente uma falha no C ++ 11 - como outros explicaram, no C ++ 11 uma variável de membro constexpr estática, diferente de qualquer outro tipo de variável global constexpr, possui ligação externa, portanto deve ser explicitamente definida em algum lugar.

Também vale a pena notar que, na prática, você pode, na prática, se livrar de variáveis ​​de membro constexpr estáticas sem definições ao compilar com otimização, pois elas podem acabar embutidas em todos os usos, mas se você compilar sem otimização, muitas vezes seu programa falhará ao vincular. Isso torna uma armadilha oculta muito comum - seu programa é compilado com otimização, mas assim que você desativa a otimização (talvez para depuração), ele não consegue se conectar.

Boas notícias - essa falha foi corrigida no C ++ 17! Entretanto, a abordagem é um pouco complicada: no C ++ 17, as variáveis ​​de membro constexpr estáticas estão implicitamente alinhadas . A aplicação embutida nas variáveis é um novo conceito no C ++ 17, mas significa efetivamente que elas não precisam de uma definição explícita em lugar algum.

SethML
fonte
4
Disponível para informações sobre C ++ 17. Você pode adicionar essas informações à resposta aceita!
SR
5

A solução mais elegante não é mudar char[]para:

static constexpr char * baz = "quz";

Dessa forma, podemos ter a definição / declaração / inicializador em 1 linha de código.

deddebme
fonte
9
with char[]você pode usar sizeofpara obter o comprimento da string em tempo de compilação, com char *você não pode (ele retornará a largura do tipo de ponteiro, 1 neste caso).
gnzlbg
2
Isso também gera aviso se você quiser ser rigoroso com a ISO C ++ 11.
Shital Shah
Veja a minha resposta que não apresentar o sizeofproblema, e pode ser usado em "header-apenas" soluções
Josh Greifer
4

Minha solução alternativa para o vínculo externo de membros estáticos é usar constexprgetters de membros de referência (que não se deparam com o problema @gnzlbg levantado como um comentário à resposta de @deddebme).
Esse idioma é importante para mim porque detesto ter vários arquivos .cpp em meus projetos e tento limitar o número a um, que consiste em nada além de se #includeuma main()função.

// foo.hpp
struct foo {
  static constexpr auto& baz() { return "quz"; }
};

// some.cpp

  auto sz = sizeof(foo::baz()); // sz == 4

  auto& foo_baz = foo::baz();  // note auto& not auto
  auto sz2 =  sizeof(foo_baz);    // 4
  auto name = typeid(foo_baz).name();  // something like 'char const[4]'
Josh Greifer
fonte
-1

No meu ambiente, a versão do gcc é 5.4.0. Adicionar "-O2" pode corrigir esse erro de compilação. Parece que o gcc pode lidar com esse caso ao solicitar otimização.

Haishan Zhou
fonte