Por que as mensagens de erro do modelo C ++ são tão horríveis?

28

Os modelos C ++ são notórios por gerar mensagens de erro longas e ilegíveis. Eu tenho uma idéia geral de por que as mensagens de erro do modelo em C ++ são tão ruins. Essencialmente, o problema é que o erro não é acionado até que o compilador encontre uma sintaxe que não é suportada por um determinado tipo de modelo. Por exemplo:

template <class T>
void dosomething(T& x) { x += 5; }

Se Tnão suportar o +=operador, o compilador gerará uma mensagem de erro. E se isso acontecer dentro de uma biblioteca em algum lugar, a mensagem de erro pode ter milhares de linhas.

Mas os modelos C ++ são essencialmente apenas um mecanismo para digitação de patos em tempo de compilação. Um erro de modelo C ++ é conceitualmente muito semelhante a um erro de tipo de tempo de execução que pode ocorrer em uma linguagem dinâmica como Python. Por exemplo, considere o seguinte código Python:

def dosomething(x):
   x.foo()

Aqui, se xnão houver um foo()método, o interpretador Python lança uma exceção e exibe um rastreamento de pilha junto com uma mensagem de erro bastante clara indicando o problema. Mesmo que o erro não seja acionado até que o intérprete esteja profundamente dentro de alguma função da biblioteca, a mensagem de erro de tempo de execução ainda não será tão ruim quanto o vômito ilegível emitido por um compilador C ++ típico. Então, por que um compilador C ++ não pode ser mais claro sobre o que deu errado? Por que algumas mensagens de erro do modelo C ++ literalmente fazem com que a janela do meu console role por mais de 5 segundos?

Channel72
fonte
6
Alguns compiladores têm mensagens de erro horríveis, mas outros são realmente bons ( clang++wink wink).
22711 Benjamin Bannier
2
Então, você prefere que seus programas falhem no tempo de execução, entregues nas mãos de um cliente, em vez de falhar no tempo de compilação?
precisa saber é o seguinte
13
@ Pavel, não. Esta pergunta não é sobre as vantagens / desvantagens do tempo de execução versus verificação de erros em tempo de compilação.
precisa saber é o seguinte
1
Como um exemplo de erros molde grandes C ++, Fwiw: codegolf.stackexchange.com/a/10470/7174
KEBS

Respostas:

28

As mensagens de erro do modelo podem ser notórias, mas nem sempre são longas e ilegíveis. Nesse caso, a mensagem de erro inteira (do gcc) é:

test.cpp: In function void dosomething(T&) [with T = X]’:
test.cpp:11:   instantiated from here
test.cpp:6: error: no match for operator+=’ in x += 5

Como no exemplo do Python, você obtém um "rastreamento de pilha" dos pontos de instanciação do modelo e uma clara mensagem de erro indicando o problema.

Às vezes, as mensagens de erro relacionadas ao modelo podem demorar muito mais, por vários motivos:

  • O "rastreamento de pilha" pode ser muito mais profundo
  • Os nomes dos tipos podem ser muito mais longos, pois os modelos são instanciados com outras instanciações de modelo como seus argumentos e exibidos com todos os seus qualificadores de namespace
  • Quando a resolução da sobrecarga falha, a mensagem de erro pode conter uma lista de sobrecargas candidatas (que podem conter nomes de tipos muito longos)
  • O mesmo erro pode ser relatado várias vezes, se um modelo inválido for instanciado em muitos lugares

A principal diferença do Python é o sistema de tipo estático, levando à necessidade de incluir os nomes de tipo (às vezes longos) na mensagem de erro. Sem eles, às vezes seria muito difícil diagnosticar por que a resolução da sobrecarga falhou. Com eles, seu desafio não é mais adivinhar onde está o problema, mas decifrar os hieróglifos que lhe dizem onde está.

Além disso, a verificação no tempo de execução significa que o programa será interrompido no primeiro erro encontrado, exibindo apenas uma única mensagem. Um compilador pode exibir todos os erros que encontrar, até que desista; pelo menos em C ++, ele não deve parar no primeiro erro no arquivo, pois isso pode ser uma consequência de um erro posterior.

Mike Seymour
fonte
4
Você poderia dar um exemplo de um erro como consequência de um erro posterior?
Ruslan
12

Algumas das razões óbvias incluem:

  1. História. Quando o gcc, o MSVC etc. eram novos, eles não podiam usar muito espaço extra para armazenar dados e produzir melhores mensagens de erro. A memória era escassa o suficiente para que eles simplesmente não pudessem.
  2. Durante anos, os consumidores ignoraram a qualidade das mensagens de erro, então os fornecedores também o fizeram.
  3. Com algum código, o compilador pode sincronizar novamente e diagnosticar erros reais posteriormente no código. Os erros nos modelos são tão graves que qualquer coisa após o primeiro é quase sempre inútil.
  4. A flexibilidade geral dos modelos torna difícil adivinhar o que você provavelmente quis dizer quando seu código tem um erro.
  5. Dentro de um modelo, o significado de um nome depende do contexto do modelo e do contexto da instanciação, e a pesquisa dependente de argumento pode adicionar ainda mais possibilidades.
  6. A sobrecarga de função pode fornecer muitos candidatos para o que uma chamada de função específica pode se referir, e alguns compiladores (por exemplo, gcc) listam todos eles obedientemente quando há uma ambiguidade.
  7. Muitos codificadores que nunca consideraram o uso de parâmetros normais sem garantir que os valores passados ​​atendam aos requisitos nem sequer tentam verificar os parâmetros do modelo (e eu tenho que confessar, eu mesmo tendem a isso).

Isso está longe de ser exaustivo, mas você entende a ideia geral. Mesmo que não seja fácil, a maior parte pode ser curada. Por anos, venho dizendo às pessoas para obter uma cópia do Comeau C ++ para uso regular; Provavelmente salvei o suficiente de uma mensagem de erro uma vez para pagar pelo compilador. Agora Clang está chegando ao mesmo ponto (e é ainda mais barato).

Termino com uma observação geral que soa como uma piada, mas realmente não é. Na maioria das vezes, o trabalho real de um compilador honestamente é transformar código fonte em mensagens de erro. Já é tempo de os fornecedores se concentrarem em fazer esse trabalho um pouco melhor - embora eu admita abertamente que, quando escrevi compiladores, tive uma forte tendência a tratá-lo como secundário (na melhor das hipóteses) e, em alguns casos, quase o ignorei. completamente.

Jerry Coffin
fonte
9

A resposta é simples, porque o Python foi projetado para funcionar dessa maneira, enquanto muitas das coisas associadas aos modelos surgiram por acidente. Ele nunca teve a intenção de se tornar um sistema completo de Turing, por exemplo. E se você não pode planejar e argumentar deliberadamente sobre o que acontece quando o sistema funciona , por que alguém deveria esperar um planejamento cuidadoso e cuidadoso sobre o que acontece quando algo dá errado?

Além disso, como você apontou, o interpretador Python pode facilitar muito a exibição de um rastreamento de pilha, porque está interpretando o código Python. Se um compilador C ++ detectar um erro de modelo e fornecer um rastreamento de pilha, isso seria tão inútil quanto "vômito de modelo", não seria?

Mason Wheeler
fonte