Referência de trabalho: C++20, biblioteca padrão e CMake

Esta página foi escrita para consulta, e não para leitura corrida. Cada seção responde a uma pergunta de quem está com o projeto do próprio compilador aberto, como “em que unidade conta a posição”, “por que o aviso virou erro” ou “por que o teste não acha o arquivo”. A resposta traz o menor código que compila, quando há código a mostrar, o significado de cada parâmetro e o que se observa quando ele é preenchido errado.

Quando esta página foi escrita, e onde procurar o que mudou

Esta página foi escrita em 25 de setembro de 2026, por volta das dez horas, depois de um levantamento das versões instaladas que começou na noite anterior. As assinaturas, os valores padrão e as mensagens de erro desta página foram tirados das versões instaladas na máquina em que o projeto de referência deste livro é construído, e todo exemplo de código foi compilado com elas. Onde o texto diz “na verificação desta página”, fala dessas compilações e das execuções feitas junto com elas. A assinatura de uma função é o nome dela seguido dos parâmetros que ela recebe, na ordem, e parâmetro é cada valor que a função pede a quem a chama.

Peça Versão resolvida Onde ela aparece
compilador MSVC (cl.exe) conjunto de ferramentas 14.51.36231; o próprio compilador se identifica como 19.51.36260 na primeira linha que o CMake imprime ao configurar: The CXX compiler identification is MSVC 19.51.36260.0
biblioteca padrão da Microsoft atualização 202604 na macro _MSVC_STL_UPDATE
biblioteca C do Windows (UCRT), de onde vêm strtoull, isdigit e system 10.0.28000.0 no Windows SDK, o pacote de desenvolvimento do Windows, na versão que o ambiente de desenvolvimento do MSVC seleciona (construir e testar pela linha de comando)
CMake e CTest 4.4.3 em cmake --version
Ninja, o gerador usado na verificação 1.13.2 em ninja --version
extensão CMake Tools do VS Code 1.24.42 na lista de extensões instaladas do editor

O padrão da linguagem é a norma que define o C++, e a versão dela usada aqui é o C++20. Quem pede essa versão é o arquivo de build do projeto, o arquivo que diz ao CMake o que construir. O CMake é o programa que lê o arquivo de build e prepara a construção, e ele traduz o pedido na opção -std:c++20.

O que não foi testado. O arquivo de build do projeto declara também um bloco para GCC e Clang, e nenhum dos dois estava instalado na máquina em que esta página foi escrita. Tudo o que ela afirma sobre mensagem, aviso e comportamento do programa já rodando vale para o MSVC na versão da tabela. Sobre os outros dois compiladores, ela só repete as opções que o arquivo de build declara e aponta a documentação de cada um; não afirma que os exemplos compilam neles.

Uma referência de ferramenta tem prazo de validade, e o desta é a próxima atualização do compilador ou do CMake. Quando um nome daqui não existir mais, ou uma mensagem de erro vier com outro texto, a página errada é esta, e não o seu programa. As fontes abaixo dizem o que vale hoje.

Onde O que traz
a referência do cppreference a referência mais usada da linguagem e da biblioteca padrão, organizada por cabeçalho, com a marca (C++20) em cada membro que só existe a partir dessa versão. É um wiki mantido pela comunidade, e não pelo comitê que publica o padrão da linguagem
o rascunho de trabalho do padrão da linguagem o texto normativo da linguagem, na forma de rascunho público. Serve para quando duas fontes discordam sobre o que a linguagem garante
a referência da biblioteca padrão da Microsoft a mesma biblioteca, descrita pela implementação que acompanha o MSVC
as opções do compilador MSVC, por categoria cada opção de linha de comando do cl.exe, com a página própria de cada uma
os erros e avisos do compilador e do ligador MSVC o índice por faixa de código — erros C2001 a C3999, avisos C4000 a C5999, erros do ligador LNK —, com uma página por diagnóstico
o registro de mudanças da biblioteca da Microsoft o que mudou em cada versão da biblioteca, agrupado por versão do Visual Studio. É onde se descobre o que mudou entre a instalada aqui e a sua
a referência do CMake comandos, variáveis, propriedades e linha de comando, sempre na versão mais recente — que, no dia em que esta página foi escrita, era a mesma 4.4.3 da tabela acima
o tutorial do CMake o percurso introdutório oficial, em onze passos, do primeiro executável até testes com CTest e instalação
as notas de versão do CMake o que mudou em cada versão, inclusive o que deixou de ser aceito
as opções de aviso do GCC o que liga cada uma das quatro opções que o projeto declara para o GCC: -Wall, -Wextra, -Wpedantic e -Werror
a referência de diagnósticos do Clang cada aviso do Clang, agrupado pela opção que o liga, com seções para -Wall, -Wextra e -Wpedantic
a documentação da extensão CMake Tools como a extensão do VS Code escolhe compilador, gerador e presets

Os doze endereços foram abertos e conferidos no dia em que esta página foi escrita. Dois candidatos ficaram de fora, porque nenhum dos dois entrega o que promete: o endereço antigo da referência do cppreference, que hoje redireciona para o atual, e a versão em português do mesmo site, que devolve a página em inglês.

Todas as fontes estão em inglês. A documentação da Microsoft tem versão em português, produzida por tradução automática; na dúvida entre as duas, vale a em inglês, que é a original.

Como conferir a versão que o seu projeto usa, em vez de confiar nesta página. O arquivo de build declara o que o projeto pede: cmake_minimum_required diz a menor versão do CMake aceita, e CMAKE_CXX_STANDARD diz a versão do padrão da linguagem pedida. A versão usada de fato fica registrada na pasta de build, a pasta onde o CMake grava tudo o que gera, na primeira vez que o CMake prepara essa pasta. O CMakeCache.txt guarda o caminho completo do compilador em CMAKE_CXX_COMPILER e a versão do CMake nas três linhas CMAKE_CACHE_MAJOR_VERSION, CMAKE_CACHE_MINOR_VERSION e CMAKE_CACHE_PATCH_VERSION. O arquivo CMakeCXXCompiler.cmake, dentro de CMakeFiles/<versão do CMake>/, guarda CMAKE_CXX_COMPILER_ID e CMAKE_CXX_COMPILER_VERSION. O arquivo de build diz o que o projeto pede; a pasta de build diz o que foi usado. Se a sua versão for mais nova que a desta página e algo divergir, quem está desatualizada é esta página.

Como esta referência está montada

Toda tabela de parâmetros desta página tem as quatro colunas abaixo.

Coluna O que traz
Parâmetro ou Opção o nome exato, como o código ou a linha de comando o escreve
O que é, com a unidade o significado, com a unidade quando existe uma
Padrão o valor assumido quando o parâmetro é omitido, ou a palavra “obrigatório”
Se errar o sintoma concreto de um valor mal escolhido: a mensagem que aparece, ou o que o programa faz de diferente

Os nomes de parâmetro de função são os que a biblioteca instalada usa ao declarar a função. Em C++, quem chama uma função escreve os valores na ordem dos parâmetros, e cada valor desses é um argumento; o nome do parâmetro não aparece na chamada. A coluna Parâmetro traz o nome que o cabeçalho da biblioteca da Microsoft dá a cada parâmetro, como _Off, _Count e _Ch. O cabeçalho é o arquivo que declara o que outros arquivos podem usar, e o nome escrito nele é o que aparece nas mensagens do compilador de C++ e na dica que o editor mostra ao passar o cursor. As páginas de documentação listadas em onde procurar o que mudou usam outros nomes para os mesmos parâmetros; a ordem, entre parênteses (1º, 2º, 3º), é o que não muda.

Unidades. Nesta página, posição é o lugar de um elemento numa sequência, contado a partir de zero (o primeiro elemento está na posição 0), e comprimento, ou tamanho, é uma quantidade de elementos; num std::string, o tipo com que a biblioteca padrão guarda texto, os dois se medem em bytes, e não em letras (a razão está em eixos e convenções). A biblioteca padrão é o código pronto que acompanha o compilador. Um caminho de arquivo relativo é o que não diz a pasta inteira desde o disco, como exemplos/entrada.txt. Ele é procurado a partir do diretório de trabalho, a pasta de onde o programa é executado, e não a partir da pasta onde está o executável, o arquivo do programa pronto para rodar.

As mensagens de erro estão transcritas como as ferramentas as imprimiram, em inglês, porque é a forma em que você vai procurá-las.

Onde procurar

A pergunta A seção
o que é unidade de tradução, cabeçalho, ligador, alvo o vocabulário mínimo
a posição começa em zero ou em um, e ela conta letras ou bytes eixos e convenções
como compilo e rodo os testes pela linha de comando construir e testar pela linha de comando
o que cada linha do arquivo de build faz o arquivo de build
como faço de uma execução do meu programa um teste transformar uma execução em teste
por que o teste não acha o arquivo que o programa acha quando rodado à mão transformar uma execução em teste
como testo que o meu compilador recusa uma entrada inválida transformar uma execução em teste
por que um aviso impediu a compilação, e o que cada opção de aviso liga o modo estrito do compilador
o que o VS Code faz quando abro a pasta do projeto construir pelo editor
como leio para a memória, inteiro, o texto que o meu compilador vai traduzir ler um arquivo
por que aparece um \r no fim de cada linha ler um arquivo
como gravo o que o meu compilador gerou, criando a pasta gravar um arquivo
como ando pelo texto e sei em que linha e coluna estou andar pelo texto
como recorto um pedaço do texto e procuro um caractere andar pelo texto
como sei se um caractere é letra, dígito ou espaço classificar um caractere
como transformo o texto "123" no número 123 converter texto em número
como alinho colunas numa listagem montar a saída
onde guardo estados, transições e símbolos do alfabeto guardar uma sequência
como associo um nome a um valor, ou um conjunto de estados a um número mapas e conjuntos
como monto a árvore sintática sem vazar memória a árvore sintática
como ordeno e tiro os repetidos ordenar, procurar, remover
como recebo o nome do arquivo e devolvo sucesso ou falha a função main
como chamo outro compilador de dentro do meu chamar outro programa
o que o teste faz quando o programa que ele chama não está instalado chamar outro programa
deu erro, e não sei por onde começar quando não funciona
o que significa aquela palavra o glossário

O vocabulário mínimo

Catorze palavras, algumas em par, sustentam o resto. Elas aparecem nas mensagens do compilador de C++, na saída do CMake e em toda a documentação. Sem separá-las, você lê o erro sem saber em que etapa ele aconteceu.

Unidade de tradução, em inglês translation unit, é o que o compilador processa de uma vez: um arquivo .cpp depois que todos os #include foram colados dentro dele. Cada .cpp é compilado sozinho, sem saber que os outros existem. Não é o projeto, e não é o arquivo como você o vê no editor.

Cabeçalho (.h), em inglês header, é o arquivo que declara o que outros arquivos podem usar. Ele não é compilado por conta própria: entra, por #include, em cada unidade de tradução que precisa dele.

Declaração, em inglês declaration, diz que um nome existe e qual é o tipo dele — int analisar(int x);. Definição, em inglês definition, entrega o corpo, as instruções que a função executa. Uma função pode ser declarada em muitos lugares e definida em um só. Declarar sem definir compila; se a função for chamada, o erro só aparece no ligador, o programa que junta num executável o que foi compilado.

Ligador, em inglês linker, é o programa que junta os arquivos-objeto num executável. O arquivo-objeto é o que o compilador produz de cada unidade de tradução. O ligador também liga cada nome declarado num arquivo à definição que está em outro. Os erros dele começam por LNK, e não por C: LNK2019: unresolved external symbol quer dizer que alguém chamou uma função cuja definição não entrou no executável. Não é erro de sintaxe: o que o conserta é a definição entrar no executável — por exemplo, o .cpp que a contém entrar na lista de add_executable (o executável e as opções dele).

Alvo, em inglês target, é aquilo que o CMake sabe construir — um executável declarado por add_executable, por exemplo — junto com a lista dos seus .cpp e das suas opções de compilação. As opções pertencem a um alvo, e não ao projeto inteiro.

Configurar, em inglês configure, é a etapa em que o CMake lê o arquivo de build (o arquivo que diz a ele o que construir), descobre o compilador e gera os arquivos da ferramenta de construção. Construir, em inglês build, é a etapa em que essa ferramenta chama o compilador e o ligador. Erro com CMake Error é da primeira; erro com código C ou LNK é da segunda. Nesta página, a etapa de configurar se chama sempre “etapa de configuração”, para não se confundir com a configuração de build, dois verbetes abaixo.

Gerador, em inglês generator, é a parte do CMake que escreve os arquivos de uma ferramenta de construção. Cada gerador tem um nome, como Ninja, Ninja Multi-Config, Visual Studio 18 2026 e Unix Makefiles, e a escolha dele decide que ferramenta vai construir. O gerador é fixado na primeira vez que se configura uma pasta de build, a pasta onde o CMake grava o que gera.

Configuração de build, em inglês build configuration, é o tipo de construção escolhido — Debug, Release, RelWithDebInfo ou MinSizeRel — e decide quanto o compilador otimiza e o que a biblioteca confere enquanto o programa roda. Não é a etapa de configuração, embora no Ninja simples a configuração de build se escolha durante essa etapa; no Ninja Multi-Config, ela se escolhe ao construir (construir e testar pela linha de comando). Em Debug, a biblioteca da Microsoft verifica cada acesso a um elemento, pelo número dele (v[i]) ou por iterador (o objeto que aponta um elemento), e interrompe o programa quando o acesso está fora do intervalo; em Release, o [] e os iteradores deixam de ser verificados, e o programa segue lendo memória que não é dele. O acesso por at(), no lugar de [], é verificado nas duas.

Aviso, em inglês warning, é uma mensagem do compilador de C++ que não impede a compilação; erro, em inglês error, é a que impede. Sob /WX, que o projeto liga, a distinção desaparece: todo aviso vira erro, e é por isso que o código deste livro tem de compilar sem aviso nenhum.

Comportamento indefinido, em inglês undefined behavior, é o que acontece quando o programa faz algo incorreto para o qual a linguagem não exige nada, nem que o compilador avise, como ler além do fim de um std::vector ou usar um ponteiro, a variável que guarda o endereço de um dado na memória, depois de esse dado ter sido liberado. O programa pode funcionar, travar ou dar resultado errado, e pode mudar de comportamento entre Debug e Release. Não é o mesmo que erro: nada garante que ele produza mensagem alguma.

Padrão da linguagem, em inglês standard, é a norma que define o C++; o C++20 é uma versão dela. Não é o “Padrão” que dá nome à terceira coluna das tabelas de parâmetro desta página: esse é o valor assumido quando o parâmetro é omitido, em inglês default.

Eixos e convenções

Convenção Valor Consequência prática
Primeira posição 0 o último caractere de um texto de tamanho n está na posição n - 1
Tipo de tamanho e posição std::size_t, sem sinal 0 - 1 não dá -1: dá 18446744073709551615, o maior valor do tipo, e o laço que conta para trás até -1 nunca termina
Unidade de std::string byte "ação" tem seis de tamanho, e não quatro: ç e ã ocupam 2 bytes cada em UTF-8
Codificação do fonte e dos literais UTF-8, pela opção /utf-8 o texto entre aspas no seu código vira bytes UTF-8 no executável
char tem sinal? sim, no MSVC (a ajuda do cl lista /J para torná-lo sem sinal) um byte de letra acentuada vira um char negativo, e passá-lo direto para std::isalpha interrompe o programa em Debug (classificar um caractere)
Intervalo de iteradores semiaberto, [first, last) last aponta um depois do último elemento; end() nunca se lê
“Não encontrado”, em texto std::string::npos é o maior valor de std::size_t, e não -1; comparar com -1 funciona só porque o -1 é convertido para std::size_t e vira esse mesmo maior valor
Fim de linha no Windows 2 bytes, \r\n lido em modo binário, o \r chega ao seu analisador léxico (ler um arquivo)
Caminho relativo procurado a partir do diretório de trabalho o mesmo executável acha o arquivo quando rodado da raiz do projeto e não acha quando rodado pelo CTest sem WORKING_DIRECTORY (transformar uma execução em teste)
Código de saída do programa 0 é sucesso; qualquer outro valor é falha o CTest marca o teste como Failed pelo código de saída, e não pelo que o programa escreveu
Separador decimal na conversão de texto ponto std::strtod lê "3,14" como 3 e para na vírgula

A convenção mais cara de desrespeitar é a do tipo sem sinal, o tipo que não guarda número negativo. Ela custa caro porque nem toda violação dela produz aviso sob este compilador. Guardar um tamanho num int produz C4267 (o modo estrito do compilador), mas o laço for (int i = 0; i < v.size(); ++i) compilou limpo sob /W4 /WX na verificação desta página. Nesse laço, com i começando em zero e só subindo, comparar o int com o tamanho, que é sem sinal, é inofensivo; o compilador também não avisa quando a comparação deixa de ser inofensiva. Na comparação, o int é convertido para std::size_t, e o valor só muda quando ele é negativo: um int que vale -1 vira 18446744073709551615, e i < v.size() deu falso num vetor de três elementos. O mesmo número sai de v.size() - 1 com o vetor vazio: a subtração não dá -1, e o laço que vai até v.size() - 1 ganha esse número como limite. Nos dois casos o defeito só aparece com o programa já rodando. Os outros dois compiladores que o projeto declara não foram testados aqui, e a ausência de aviso no MSVC não diz nada sobre eles.

Construir e testar pela linha de comando

Rode os três comandos do bloco abaixo nesta ordem, da pasta que contém o arquivo de build de topo e num terminal em que o compilador esteja acessível. O arquivo de build é o arquivo que diz ao CMake o que construir, e o de topo é o CMakeLists.txt da raiz do projeto, já que cada subpasta acrescentada por add_subdirectory pode ter o seu.

No Windows, o terminal em que o compilador está acessível é o ambiente de desenvolvimento do MSVC: um terminal em que o cl (o comando do compilador) e o ligador são encontrados pelo PATH, a lista de pastas em que o terminal procura os programas, e em que as variáveis de ambiente INCLUDE e LIB, vazias num terminal comum, vêm preenchidas com as pastas de cabeçalhos e de bibliotecas. O ligador é o programa que junta num executável o que foi compilado. O ambiente de desenvolvimento se abre pelo atalho x64 Native Tools Command Prompt for VS, no menu Iniciar. Fora do ambiente de desenvolvimento, com o Ninja Multi-Config dos comandos abaixo, o primeiro comando, que faz a etapa de configuração, termina com No CMAKE_CXX_COMPILER could be found. O VS Code com a extensão CMake Tools monta esse mesmo ambiente sozinho (construir pelo editor).

cmake -S . -B build -G "Ninja Multi-Config"
cmake --build build --config Debug
ctest --test-dir build -C Debug --output-on-failure

O primeiro configura: o CMake lê o arquivo de build, descobre o compilador e gera os arquivos da ferramenta de construção, o programa que vai chamar o compilador e o ligador (aqui, o Ninja). O primeiro comando só precisa ser rodado uma vez: depois dele, cmake --build refaz a etapa de configuração sozinho quando o arquivo de build muda, e imprime Re-running CMake... quando o faz. O segundo constrói: a ferramenta de construção chama o compilador e o ligador. O terceiro roda os testes registrados no arquivo de build.

Opção O que é, com a unidade Padrão Se errar
-S <pasta-do-fonte> a pasta que contém o arquivo de build de topo, o CMakeLists.txt a pasta corrente apontada para uma pasta sem arquivo de build, o CMake para com does not appear to contain CMakeLists.txt
-B <pasta-de-build> a pasta onde o CMake grava tudo o que gera: cache, arquivos do gerador, objetos e executáveis a pasta corrente sem ela, e com o comando rodado da raiz do projeto, o cache, os objetos e o executável se espalham entre os seus .cpp
-G <nome-do-gerador> a ferramenta de construção para a qual o CMake gera arquivos, com o nome exato da lista de cmake --help o gerador marcado com * naquela lista; na máquina desta página, Visual Studio 18 2026 nome fora da lista dá Could not create named generator; gerador diferente do usado na primeira vez que a pasta de build foi configurada dá Does not match the generator used previously; omitida, vale o gerador do Visual Studio, que na máquina desta página parou a etapa de configuração com is not able to compile a simple test program (o parágrafo O gerador do Visual Studio sem o Visual Studio, logo abaixo da tabela)
--build <pasta-de-build> constrói uma pasta de build já configurada obrigatório pasta inexistente dá is not a directory
--config <configuração> qual configuração de build construir, em gerador de várias configurações de build Debug, no Ninja Multi-Config em gerador de uma configuração de build só, como o Ninja simples, é ignorado sem aviso: --config Release responde no work to do e o executável continua sendo o de Debug
--test-dir <pasta-de-build> a pasta de build onde procurar os testes a pasta corrente pasta inexistente dá Failed to change working directory; pasta sem teste registrado dá No tests were found!!!
-C <configuração> qual configuração de build testar, em gerador de várias configurações de build nenhuma omitida, cada teste sai ***Not Run com Test not available without configuration., seguido de uma dica que pede a opção -C
--output-on-failure mostra o que o programa escreveu quando o teste falha desligado sem ela, o teste que falha mostra só ***Failed, e a mensagem do seu programa fica no arquivo LastTest.log, dentro de Testing/Temporary/ na pasta de build
-R <expressão> roda só os testes cujo nome casa a expressão regular todos os testes expressão que não casa nome nenhum dá No tests were found!!!, a mesma mensagem da pasta errada

Gerador de uma configuração de build e gerador de várias. O gerador é a parte do CMake que escreve os arquivos da ferramenta de construção, e se escolhe por -G. A configuração de build é o tipo de construção escolhido, que decide quanto o compilador otimiza e o que a biblioteca confere enquanto o programa roda. No Ninja simples, no NMake e no Unix Makefiles, a configuração de build se escolhe na etapa de configuração, por -DCMAKE_BUILD_TYPE=Release; com o MSVC e sem essa variável, a verificação desta página obteve Debug. No Ninja Multi-Config e no Visual Studio, a configuração de build se escolhe ao construir, por --config, e ao testar, por -C; no Ninja Multi-Config, conferido aqui, o executável sai numa subpasta com o nome dela, build/Debug/. Misturar as duas formas leva ao caso da tabela acima: num gerador de uma configuração de build só, --config Release é ignorado, e nada muda.

O gerador do Visual Studio sem o Visual Studio. Na máquina desta página, que tem só as ferramentas de build do Visual Studio, e não o Visual Studio inteiro, o gerador padrão é o Visual Studio 18 2026. Com ele, a etapa de configuração falhou, dentro e fora do ambiente de desenvolvimento, com is not able to compile a simple test program. Logo abaixo, na mesma mensagem, vêm Run Build Command(s): devenv.com e, na linha seguinte, no such file or directory. O devenv.com é o comando que a documentação do CMake descreve como instalado com o Visual Studio; a instalação só das ferramentas de build não o traz, e o CMake não o encontra quando tenta construir o programa de teste com que confere o compilador. Nesse caso, troque de gerador, pedindo o Ninja Multi-Config por -G "Ninja Multi-Config", e rode os comandos dentro do ambiente de desenvolvimento, como nesta seção.

Pasta profunda demais. Para cada arquivo de código que compila, o compilador produz um arquivo, chamado arquivo-objeto, que fica dentro da pasta de build. O caminho completo de cada arquivo-objeto tem um limite de caracteres, que o CMake traz embutido para cada plataforma e que a variável CMAKE_OBJECT_PATH_MAX permite fixar à mão. Quando o projeto mora numa pasta profunda demais no disco, de caminho comprido, a etapa de configuração avisa que o caminho da pasta dos arquivos-objeto já é tão longo que um arquivo-objeto dentro dela passaria desse limite. A mensagem começa por The object file directory e dá o caminho dessa pasta, quantos caracteres ele tem e o máximo. Na máquina desta página, o máximo veio como The maximum full path to an object file is 250 characters (see CMAKE_OBJECT_PATH_MAX). A mensagem termina em The build may not work correctly. Quando ela aparecer, mova o projeto para uma pasta de caminho mais curto.

O arquivo de build

Você diz ao CMake o que construir, e com que opções, num arquivo de build. O bloco abaixo é o menor arquivo de build com as mesmas peças do projeto de referência: um executável, o padrão C++20 fixado, os avisos ligados com as opções de cada compilador e um teste.

cmake_minimum_required(VERSION 3.15)
project(meucompilador VERSION 0.1.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

add_executable(meucompilador
    main.cpp
    lexico.cpp
)

if(MSVC)
    target_compile_options(meucompilador PRIVATE /W4 /WX /permissive- /utf-8 /EHsc)
else()
    target_compile_options(meucompilador PRIVATE -Wall -Wextra -Wpedantic -Werror)
endif()

enable_testing()
add_test(NAME lexico COMMAND meucompilador exemplos/entrada.txt)
set_tests_properties(lexico PROPERTIES WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")

O cabeçalho do arquivo

cmake_minimum_required(VERSION <versão-mínima>) e project(<nome-do-projeto> VERSION <versão> LANGUAGES <linguagens-do-projeto>...)

Parâmetro O que é, com a unidade Padrão Se errar
VERSION <versão-mínima> (em cmake_minimum_required) a menor versão do CMake aceita, na forma maior.menor[.correção]; ela também decide quais políticas — regras de compatibilidade — valem no arquivo obrigatório abaixo de 3.15, como em 3.10.0, a política CMP0092, que entrou na 3.15, fica no comportamento antigo, e o CMake põe /W3 nas opções padrão; o /W4 do arquivo de build o substitui, e cada .cpp compilado imprime Command line warning D9025 : overriding '/W3' with '/W4', inofensivo mas presente em toda saída. Com 3.15 ou mais, como no exemplo acima, o aviso some
<nome-do-projeto> (em project) o nome do projeto, guardado em PROJECT_NAME obrigatório é só um nome; o do executável vem de add_executable, e trocar um não troca o outro
VERSION (em project) a versão do seu programa, na forma maior.menor.correção vazia omitida, as variáveis de versão do projeto ficam vazias, e nada mais muda
LANGUAGES as linguagens para as quais o CMake procura compilador C e CXX omitida, a etapa de configuração identifica também um compilador de C, que um projeto só de .cpp não usa; numa máquina sem compilador de C, é essa procura que falha

O padrão da linguagem

Três variáveis tratam do padrão da linguagem, que é a norma que define o C++: dizem ao CMake qual versão dela pedir ao compilador, e de que modo. As três se escrevem com set antes de add_executable: cada uma é copiada para o alvo, que aqui é o executável, no momento em que ele é criado.

Parâmetro O que é, com a unidade Padrão Se errar
CMAKE_CXX_STANDARD o padrão da linguagem pedido para os alvos criados depois; 20 vira -std:c++20 na linha do MSVC não definida: o compilador usa o padrão da linguagem que adota por conta própria, registrado como 14 no arquivo de identificação do compilador desta versão escrito depois de add_executable, não alcança o alvo já criado, e todo membro do C++20 é recusado: sob C++14, s.starts_with("ab") deu error C2039: 'starts_with': is not a member of, seguido do nome completo do tipo std::basic_string
CMAKE_CXX_STANDARD_REQUIRED se o padrão da linguagem pedido é exigência (ON) ou preferência (OFF) não definida, o que vale como OFF com OFF, um compilador sem suporte a C++20 recebe silenciosamente uma versão anterior do padrão da linguagem, e o erro aparece como membro inexistente no meio do código, e não como falta de suporte na etapa de configuração
CMAKE_CXX_EXTENSIONS se o CMake pede a variante do padrão da linguagem com as extensões do compilador não definida, o que vale como ON no MSVC desta versão, a linha de compilação saiu igual com ON e com OFF, -std:c++20 nas duas. Em compiladores que têm variante com extensões, a documentação do CMake dá como efeito de ON a troca de -std=c++11 por -std=gnu++11: com ON, o código que usa uma extensão própria desse compilador compila nele, e o mesmo código pode ser recusado pelo MSVC

O executável e as opções dele

add_executable(<nome-do-alvo> <fontes-do-alvo>...) e target_compile_options(<nome-do-alvo> PRIVATE <opção>...)

Parâmetro O que é, com a unidade Padrão Se errar
<nome-do-alvo> (em add_executable) o nome do alvo; o arquivo gerado tem esse nome seguido de .exe, no Windows obrigatório é por este nome que target_compile_options e add_test se referem ao alvo; escrito diferente em target_compile_options, a etapa de configuração para com Cannot specify compile options for target "progx" which is not built by this project. (progx era o nome escrito errado)
<fontes-do-alvo> os .cpp que entram no executável, com caminho relativo à pasta do arquivo de build nenhum .cpp esquecido na lista compila tudo o mais e falha no ligador com LNK2019: unresolved external symbol, citando a função que mora nele
<nome-do-alvo> (em target_compile_options) o alvo que recebe as opções; precisa já ter sido criado por add_executable obrigatório escrito antes do add_executable, a etapa de configuração para com a mesma mensagem do nome errado, Cannot specify compile options for target: naquele ponto o alvo ainda não existe
PRIVATE as opções valem só para compilar este alvo, e não para quem depender dele obrigatório: um de PRIVATE, PUBLIC ou INTERFACE omitido, a etapa de configuração para com target_compile_options called with invalid arguments

A condição if(MSVC), que separa os dois blocos de opções, é verdadeira quando o compilador é o MSVC ou outro que imita a linha de comando do cl. No exemplo, as opções escritas para o MSVC começam por /, e as escritas para o GCC e o Clang começam por -; trocar os dois blocos de lugar entrega a cada compilador opções que ele não conhece.

Transformar uma execução em teste

enable_testing(), add_test(NAME <nome-do-teste> COMMAND <programa-ou-alvo> [<argumentos-do-programa>...]) e set_tests_properties(<nome-do-teste> PROPERTIES <nome-da-propriedade> <valor-da-propriedade>...)

O fim do exemplo da seção o arquivo de build, de enable_testing() em diante, registra um teste, e esta seção explica cada peça desse trecho. O CTest é o programa que roda os testes registrados no arquivo de build, e um teste dele é uma execução do seu programa com argumentos fixos. Os argumentos são os textos escritos depois do nome do programa, como o exemplos/entrada.txt do exemplo. Ao terminar, o seu programa devolve um número ao sistema, o código de saída. O teste passa quando o código de saída é 0 e falha com qualquer outro (a função main), a menos que uma das três últimas linhas da tabela abaixo, que são propriedades do teste (ajustes escritos em set_tests_properties), mude esse critério. Com o add_test sozinho, o CTest não lê o que o programa escreveu: um programa que imprime ERRO e sai com 0 passa.

Parâmetro O que é, com a unidade Padrão Se errar
enable_testing() liga o registro de testes na pasta deste arquivo de build e nas que ficam abaixo dela; deve ficar no arquivo de build de topo desligado ausente, todo add_test é ignorado sem erro, e o ctest responde No tests were found!!!; include(CTest) também o liga
NAME <nome-do-teste> o nome do teste, usado por ctest -R e por set_tests_properties obrigatório dois testes com o mesmo nome param a etapa de configuração com add_test given test NAME "a" which already exists in this directory. (a era o nome repetido); nome escrito diferente em set_tests_properties, com set_tests_properties Can not find test to add properties to: b (b era o nome escrito errado)
COMMAND <programa-ou-alvo> o programa a rodar; quando é o nome de um alvo de add_executable, o CMake o troca pelo caminho do executável construído obrigatório escrito como caminho de arquivo (build/Debug/meucompilador.exe) em vez do nome do alvo, não é trocado: o CTest roda aquele arquivo em qualquer configuração de build, e ctest -C Release testa o executável de Debug
<argumentos-do-programa>... o que o programa recebe em argv[1], argv[2] e seguintes nenhum caminho relativo aqui é resolvido pelo diretório de trabalho, e não pela pasta do arquivo de build
WORKING_DIRECTORY a pasta de onde o teste é executado a pasta de build correspondente sem ela, o programa roda dentro da pasta de build, e exemplos/entrada.txt não é encontrado — o mesmo teste passa quando rodado à mão da raiz do projeto
WILL_FAIL inverte o veredito: o teste passa quando o programa sai com código diferente de 0, e falha quando sai com 0 desligada sem ela, o teste de uma entrada inválida, que o seu compilador recusou como devia, sai ***Failed; ligada no teste de uma entrada válida, ela reprova o programa correto, também com ***Failed
PASS_REGULAR_EXPRESSION a expressão regular que a saída do programa, a normal ou a de erro, tem de casar para o teste passar; com ela, o código de saída não conta nenhuma expressão que não casa o texto que o programa escreve reprova o teste mesmo quando a entrada foi recusada; e, como o código de saída deixa de contar, o programa que escreve a mensagem e sai com 0 passa
SKIP_RETURN_CODE o código de saída, de 0 a 255, que marca o teste como pulado, e não como aprovado nem como reprovado nenhum sem ela, o teste que não pôde conferir nada só tem duas saídas: 0, que o aprova, e outro código, que o reprova (chamar outro programa)

A verificação desta página confirmou por que o teste sem WORKING_DIRECTORY não acha o arquivo. Ela imprimiu, de dentro de um teste, o diretório de trabalho, que é a pasta de onde o programa é executado e a partir da qual ele procura um caminho relativo como exemplos/entrada.txt: sem WORKING_DIRECTORY, o diretório de trabalho era a pasta de build; com "${CMAKE_CURRENT_SOURCE_DIR}", era a pasta do arquivo de build.

O teste de uma entrada que deve ser recusada. O seu compilador, quando recusa a entrada, deve sair com código diferente de zero (a função main), e o CTest, sem mais nada, reprova o teste dessa entrada. Para um teste registrado como add_test(NAME lexico_invalido COMMAND meucompilador exemplos/invalido.txt), a linha que inverte o veredito é set_tests_properties(lexico_invalido PROPERTIES WILL_FAIL TRUE WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"). Na verificação desta página, com o mesmo programa recusando o mesmo arquivo, o CTest marcou o teste ***Failed sem WILL_FAIL e Passed com ela. WILL_FAIL aprova qualquer recusa, inclusive a recusa por um motivo que não é o testado — um arquivo que não abriu, por exemplo. Para exigir o motivo, use, no lugar de WILL_FAIL, a propriedade PASS_REGULAR_EXPRESSION, com um trecho da mensagem de erro do seu compilador: na mesma verificação, o mesmo teste, com PASS_REGULAR_EXPRESSION "erro lexico" e sem WILL_FAIL, foi marcado Passed, porque o programa escreveu erro lexico antes de sair com 1.

O modo estrito do compilador

O modo estrito é o conjunto de opções de compilação que o projeto liga. Para o MSVC, são /W4 /WX /permissive- /utf-8 /EHsc, e com elas todo aviso impede a compilação; sem elas, um aviso é uma mensagem que não a impede. Para o GCC e o Clang, o projeto declara -Wall -Wextra -Wpedantic -Werror; o que cada uma dessas quatro faz está nas duas fontes correspondentes da tabela de proveniência, e esta página não as descreve porque nenhum dos dois compiladores estava instalado para conferir.

Opção O que é, com a unidade Padrão Se errar
/W4 o nível de aviso, de 1 a 4; quanto maior, mais avisos nível 1, segundo a ajuda do cl; com cmake_minimum_required abaixo de 3.15, o CMake já põe /W3, que o /W4 substitui (o cabeçalho do arquivo) em /W3, somem os avisos de nível 4, entre eles C4100 (parâmetro não usado), C4189 (variável iniciada e não usada) e C4456 (declaração que esconde outra do mesmo nome) — e o código que passa limpo em /W3 deixa de passar em quem compila com /W4
/WX trata todo aviso como erro desligado sem ela, o executável é gerado mesmo com avisos; com ela, a lista de avisos vem precedida de error C2220: the following warning is treated as an error, e nada é gerado
/permissive- liga a conformidade estrita ao padrão da linguagem sob -std:c++20, já é o comportamento: a ajuda do cl registra o modo permissivo como desligado a partir do C++20 removida, nada muda enquanto o padrão da linguagem for C++20; sob -std:c++17 e sem ela, char* p = "abc"; compila, enquanto sob C++20 é recusado com C2440: cannot convert from 'const char [4]' to 'char *'
/utf-8 lê os arquivos .cpp e .h como UTF-8 e grava os literais de texto em UTF-8 sem ela, o conjunto de caracteres deixa de ser fixado pelo projeto e passa a ser o que o compilador assume na máquina de quem compila na máquina desta página, "ação" e "Árvore" produziram os mesmos bytes com e sem a opção; é justamente por o resultado variar de máquina para máquina que ela fica fixa no arquivo de build
/EHsc liga o tratamento de exceções de C++ (s) e supõe que funções extern "C" não lançam exceção (c) o CMake já a põe nas opções padrão do MSVC; o cl chamado sem nenhuma /EH não a tem removida do arquivo de build, nada muda, porque o CMake a põe pelas opções padrão; no cl chamado sem nenhuma /EH, todo try produz warning C4530: C++ exception handler used, but unwind semantics are not enabled. Specify /EHsc, que o /WX transforma em erro

A tabela abaixo traz avisos que o compilador de C++ pode dar no código do seu compilador, com o texto exato que o MSVC desta versão imprimiu:

Mensagem O que a provoca
C4267: 'initializing': conversion from 'size_t' to 'int', possible loss of data int n = texto.size(); — tamanho é std::size_t (eixos e convenções); declare a variável como std::size_t
C4244: 'initializing': conversion from 'double' to 'int', possible loss of data atribuir um double a um int sem converter por static_cast
C4100: 'b': unreferenced parameter parâmetro declarado e nunca usado
C4189: 'sobra': local variable is initialized but not referenced variável iniciada e nunca lida
C4456: declaration of 'total' hides previous local declaration uma variável interna ao laço com o mesmo nome de outra de fora; a de fora deixa de ser atualizada
C4715: 'sinal': not all control paths return a value função com tipo de retorno em que algum caminho chega ao fim sem return
C4858: discarding return value: This member function returns a bool indicating whether the collection is empty and has no other effects. v.empty(); escrito querendo v.clear(); — a mensagem continua e manda usar clear()
C4858: discarding return value: The 'unique' algorithm returns the iterator past the last element that should be kept. std::unique chamado sem o erase em seguida (ordenar, procurar, remover)
C4858: discarding return value: The 'remove' and 'remove_if' algorithms return the iterator past the last element that should be kept. std::remove_if chamado sem o erase em seguida; a mensagem continua e aponta std::erase_if (ordenar, procurar, remover)

O C4715 tem uma particularidade: na verificação desta página, ele não apareceu enquanto o mesmo arquivo tinha outros avisos transformados em erro, e só surgiu quando a função foi compilada sozinha. Consertados os outros avisos, conte com a possibilidade de ele surgir na compilação seguinte.

Construir pelo editor

A extensão CMake Tools do VS Code roda os mesmos comandos de construir e testar pela linha de comando a partir de botões e da paleta de comandos, a caixa em que se digita o nome de um comando do editor para executá-lo. As opções abaixo ficam nas configurações do editor. A coluna Padrão traz o valor que a extensão assume quando a opção não é definida, lido da própria extensão instalada.

Opção O que é, com a unidade Padrão Se errar
cmake.buildDirectory a pasta de build que a extensão passa ao CMake ${workspaceFolder}/build trocada, o ctest --test-dir build digitado no terminal procura na pasta antiga e responde Failed to change working directory ou No tests were found!!!
cmake.generator o gerador passado ao CMake, com o nome exato da lista de cmake --help vazio nome fora da lista faz a etapa de configuração falhar com Could not create named generator
cmake.useCMakePresets se a extensão configura pelo arquivo de presets do projeto, com os valores always, never e auto auto configurando por um arquivo de presets trazido de outra máquina, que peça um conjunto de ferramentas ausente nesta, a etapa de configuração falha com error MSB8020: The build tools for ClangCL (Platform Toolset = 'ClangCL') cannot be found — o nome muda conforme o conjunto pedido
cmake.defaultVariants a configuração de build inicial: debug, release, minsize, reldeb ou unspecified debug em release, a biblioteca deixa de verificar índices: o acesso fora do vetor, que em Debug interrompe com vector subscript out of range, passa a ler memória alheia sem mensagem nenhuma

Um preset é um conjunto nomeado de opções do CMake, guardado num arquivo do projeto. Sem presets, a extensão configura por um kit e por uma variante. O kit é o conjunto nomeado que diz com que compilador configurar, e se escolhe pelo comando Select a Kit da paleta. Um kit traz o caminho do compilador de cada linguagem, ou a instalação do Visual Studio e a arquitetura para a qual se compila, das quais a extensão monta o ambiente de desenvolvimento, em que o compilador e o ligador são encontrados. A variante é um conjunto de opções da etapa de configuração, em que o CMake lê o arquivo de build e descobre o compilador, e a principal dessas opções é a configuração de build, que é o tipo de construção escolhido; as variantes que a extensão traz de fábrica são as de cmake.defaultVariants. A descrição de cmake.useCMakePresets, na extensão, avisa que, com presets em uso, kits, variantes e parte das configurações do editor são ignorados. Um arquivo de presets copiado junto com um projeto alheio carrega o caminho do compilador da máquina de quem o escreveu.

Ler um arquivo

O exemplo lê o texto de entrada do seu compilador, inteiro e de uma vez, para um std::string, que é o tipo de texto da biblioteca:

#include <fstream>
#include <sstream>
#include <string>

bool lerArquivoInteiro(const std::string& caminho, std::string& conteudo) {
    std::ifstream arquivo(caminho, std::ios::binary);
    if (!arquivo) {
        return false;
    }
    std::ostringstream acumulado;
    acumulado << arquivo.rdbuf();
    conteudo = acumulado.str();
    return true;
}

std::ifstream(_Str, _Mode)

Parâmetro O que é, com a unidade Padrão Se errar
_Str (1º) o caminho do arquivo; aceita std::string, const char* e std::filesystem::path obrigatório caminho relativo que não leva a arquivo existente não lança exceção: o objeto nasce em estado de falha, o fluxo convertido para bool, como no if (!arquivo) do exemplo, vale false, is_open() também devolve false, e quem não testa lê um texto vazio. O caminho relativo é resolvido a partir do diretório de trabalho, e não da pasta do executável (eixos e convenções)
_Mode (2º) o modo de abertura, combinado com \|; std::ios::in é sempre acrescentado std::ios::in, o modo texto em modo texto, no Windows, cada \r\n vira \n na leitura: um arquivo de 22 bytes chegou com 20 na verificação desta página, e a posição de cada caractere na memória deixa de coincidir com a posição dele no arquivo

A declaração de std::ifstream na biblioteca instalada tem um terceiro parâmetro, _Prot, que já vem com valor padrão. O próprio cabeçalho marca esse parâmetro como extensão desta biblioteca: o padrão da linguagem não o prevê.

std::ios::binary entrega os bytes como estão, e é o que o projeto de referência usa: a posição de cada caractere na memória é a posição dele no arquivo. O preço é que, num arquivo salvo com o fim de linha do Windows, que são os 2 bytes \r\n, o \r chega como um caractere qualquer ao analisador léxico, a parte do seu compilador que agrupa os caracteres em unidades, como o nome total e o sinal = (nos manuais, cada unidade dessas é um token). O analisador que só conhece ' ', '\t' e '\n' como espaço o recusa como símbolo desconhecido, na última coluna de cada linha. O remédio é tratá-lo como espaço em branco.

Ler linha a linha tem o mesmo problema, pelo mesmo motivo, e o exemplo o resolve tirando o \r do fim de cada linha:

#include <istream>
#include <string>
#include <vector>

std::vector<std::string> separarLinhas(std::istream& entrada) {
    std::vector<std::string> linhas;
    std::string linha;
    while (std::getline(entrada, linha)) {
        if (!linha.empty() && linha.back() == '\r') {
            linha.pop_back();
        }
        linhas.push_back(linha);
    }
    return linhas;
}

std::getline(_Istr, _Str) e std::getline(_Istr, _Str, _Delim)

Parâmetro O que é, com a unidade Padrão Se errar
_Istr (1º) o fluxo de onde ler obrigatório fluxo em estado de falha — arquivo que não abriu — faz o laço não executar nenhuma vez, e o resultado é uma lista vazia sem erro
_Str (2º) onde a linha é gravada, sem o delimitador obrigatório a função apaga o conteúdo anterior antes de ler (o cabeçalho instalado chama _Str.erase()); usar a mesma variável para acumular o arquivo deixa nela só a última linha
_Delim (3º) o caractere que encerra a linha não há padrão: sem ele, vale a outra sobrecarga, que usa '\n' em arquivo aberto em modo binário, com '\n' como delimitador, o \r fica no fim de cada linha: na verificação, a linha linha um voltou com 9 bytes

Gravar um arquivo

O exemplo grava num arquivo o que o seu compilador gerou e cria antes a pasta de destino, se ela faltar:

#include <filesystem>
#include <fstream>
#include <string>
#include <system_error>

bool gravarTexto(const std::filesystem::path& destino, const std::string& texto) {
    const std::filesystem::path pasta = destino.parent_path();
    if (!pasta.empty()) {
        std::error_code falha;
        std::filesystem::create_directories(pasta, falha);
        if (falha) {
            return false;
        }
    }
    std::ofstream saida(destino, std::ios::binary);
    saida << texto;
    return static_cast<bool>(saida);
}

std::filesystem::create_directories(_Path, _Ec) e std::ofstream(_Str, _Mode)

Parâmetro O que é, com a unidade Padrão Se errar
_Path (1º de create_directories) a pasta a criar, com todas as intermediárias que faltarem obrigatório caminho vazio — o parent_path() de um nome sem pasta, como saida.txt — não é “nada a fazer”: preenche o código de erro com The system cannot find the path specified., e é por isso que o exemplo testa pasta.empty() antes
_Ec (2º de create_directories) onde a falha é relatada, em vez de exceção a outra sobrecarga, sem ele, lança std::filesystem::filesystem_error usando o retorno bool como sinal de erro: ele é false também quando a pasta já existe, e o programa recusa gravar a partir da segunda execução
_Str (1º de std::ofstream) o arquivo a gravar obrigatório pasta inexistente no caminho — que, se for relativo, parte do diretório de trabalho (eixos e convenções) — não lança exceção: o objeto nasce em estado de falha, e tudo o que se escreve nele se perde em silêncio; o exemplo descobre a falha no fim, convertendo saida para bool no return
_Mode (2º de std::ofstream) o modo de abertura; std::ios::out é sempre acrescentado std::ios::out, que apaga o conteúdo anterior na verificação, gravar xy sobre um arquivo com abcdef deixou xy; para acrescentar ao fim, o modo é std::ios::app. Sem std::ios::binary, cada \n gravado vira \r\n no Windows
Peça de std::filesystem::path O que faz
pasta / "gerado.c" junta dois trechos com o separador do sistema; no Windows, a pasta saida juntada a "gerado.c" virou saida\gerado.c
parent_path() o caminho sem o último trecho
filename() só o último trecho
std::filesystem::exists(p) se o caminho existe
std::filesystem::current_path() o diretório de trabalho atual; é o que se imprime quando um caminho relativo não é encontrado

Andar pelo texto

O analisador léxico, a parte do seu compilador que agrupa os caracteres em unidades, como o nome total e o sinal =, anda pelo texto por posição, o lugar de cada byte no texto, contado a partir de zero. A mensagem de erro do seu compilador precisa de linha e coluna, que o exemplo conta a partir de 1. O exemplo converte a posição em linha e coluna, e os nomes dele pedem atenção: a posição entra no parâmetro deslocamento, e Posicao é o nome do par de linha e coluna que a função posicaoDe devolve.

#include <cstddef>
#include <string>

struct Posicao {
    std::size_t linha;
    std::size_t coluna;
};

Posicao posicaoDe(const std::string& texto, std::size_t deslocamento) {
    Posicao p{1, 1};
    for (std::size_t i = 0; i < deslocamento && i < texto.size(); ++i) {
        if (texto[i] == '\n') {
            ++p.linha;
            p.coluna = 1;
        } else {
            ++p.coluna;
        }
    }
    return p;
}

A coluna que sai daqui conta bytes. Numa linha com ação, tudo o que vem depois dessa palavra fica duas colunas à direita do que o olho conta, porque ç e ã têm 2 bytes cada; o ^ que aponta o erro sob a linha desalinha na mesma medida.

Membro de std::string O que faz
size() o comprimento, em bytes
empty() se o comprimento é zero; não esvazia (para isso, clear())
texto[i] o byte na posição i, sem verificação em Release
at(i) o mesmo, com verificação: fora do intervalo, lança std::out_of_range com a mensagem invalid string position
back() e pop_back() o último byte, e a remoção dele
push_back(c) e += acrescentam ao fim
starts_with(prefixo) se o texto começa pelo prefixo; existe a partir do C++20
c_str() o texto como const char*, terminado em zero, para funções herdadas de C
std::string(n, c) um texto com n cópias do caractere c; é como se monta a linha de espaços que leva o ^ à coluna do erro

O exemplo corta o texto em cada ocorrência de um caractere separador e devolve as partes:

#include <cstddef>
#include <string>
#include <vector>

std::vector<std::string> separarPor(const std::string& texto, char separador) {
    std::vector<std::string> partes;
    std::size_t inicio = 0;
    std::size_t achado = texto.find(separador);
    while (achado != std::string::npos) {
        partes.push_back(texto.substr(inicio, achado - inicio));
        inicio = achado + 1;
        achado = texto.find(separador, inicio);
    }
    partes.push_back(texto.substr(inicio));
    return partes;
}

substr(_Off, _Count)

Parâmetro O que é, com a unidade Padrão Se errar
_Off (1º) a posição onde o recorte começa, em bytes a partir de zero 0 posição maior que o comprimento lança std::out_of_range com a mensagem invalid string position; igual ao comprimento é permitida e devolve texto vazio
_Count (2º) quantos bytes recortar — e não a posição final npos, até o fim passar a posição final no lugar da quantidade devolve um recorte mais longo que o esperado, que avança sobre o lexema seguinte

find(_Ch, _Off) e find(_Right, _Off)

Parâmetro O que é, com a unidade Padrão Se errar
_Ch ou _Right (1º) o caractere, ou o texto, a procurar obrigatório 'ç' entre aspas simples não cabe num char, porque são 2 bytes: find('ç') produz C4305: 'argument': truncation from 'int' to 'const _Elem' e C4309: 'argument': truncation of constant value; procurar "ç", entre aspas duplas, funciona
_Off (2º) a posição de onde a busca parte 0 esquecido dentro de um laço, a busca recomeça sempre do início e acha a mesma ocorrência: o laço não termina

find devolve uma posição, do tipo std::size_t, e std::string::npos quando não há ocorrência. Guardar num int o valor devolvido produz o aviso C4267, como acontece com todo tamanho ou posição guardado num int.

Classificar um caractere

O exemplo acha onde termina um identificador, que é um nome como o de uma variável. A partir de inicio, ele avança enquanto o byte é letra, dígito ou _, e devolve a posição do primeiro byte que não é nenhum dos três, ou o comprimento do texto, se chegar ao fim:

#include <cctype>
#include <cstddef>
#include <string>

std::size_t fimDoIdentificador(const std::string& texto, std::size_t inicio) {
    std::size_t i = inicio;
    while (i < texto.size()) {
        const unsigned char c = static_cast<unsigned char>(texto[i]);
        if (std::isalnum(c) == 0 && c != '_') {
            break;
        }
        ++i;
    }
    return i;
}

std::isalpha(_C), std::isdigit(_C), std::isalnum(_C) e std::isspace(_C)

std::isalpha pergunta se o caractere é letra; std::isdigit, se é dígito; std::isalnum, se é letra ou dígito; std::isspace, se é espaço em branco, o que inclui o espaço, a tabulação e os dois caracteres de fim de linha, \n e \r. Na tabela abaixo, asserção é uma conferência embutida no código, que interrompe o programa quando uma condição falha.

Parâmetro O que é, com a unidade Padrão Se errar
_C (1º) o caractere a classificar, como int, que tem de caber num unsigned char ou valer EOF obrigatório passado direto um char de letra acentuada, o valor chega negativo, e em Debug o programa é interrompido pela asserção c >= -1 && c <= 255; a conversão para unsigned char, no exemplo, é o que a evita

As quatro funções devolvem int, e não bool: diferente de zero quer dizer sim. Na verificação desta página, std::isalpha('a') devolveu 2, std::isdigit('5') devolveu 4 e std::isspace(' ') devolveu 8; a comparação == 1 dá falso para os três.

Elas também não reconhecem letra acentuada escrita em UTF-8, a codificação em que letra sem acento ocupa um byte e letra acentuada do português ocupa dois. Na mesma verificação, contar com std::isalpha as letras de "ação" — já com a conversão para unsigned char — deu 2: só a e o. Cada byte de ç e de ã é testado sozinho, e nenhum deles é letra. Se a linguagem que o seu compilador traduz aceitar acento nos identificadores, a regra de classificação tem de ser escrita à mão; aceitar ou não é escolha de quem projeta essa linguagem.

Converter texto em número

O exemplo converte o texto recebido em lexema, como "123", num número inteiro sem sinal. Ele devolve true só quando o texto inteiro foi lido como número e o número cabe no tipo. Para saber se coube, ele consulta errno, a variável em que std::strtoull registra o erro:

#include <cerrno>
#include <cstdlib>
#include <string>

bool lerInteiro(const std::string& lexema, unsigned long long& valor) {
    if (lexema.empty() || lexema[0] == '-' || lexema[0] == '+') {
        return false;
    }
    const char* inicio = lexema.c_str();
    char* fim = nullptr;
    errno = 0;
    valor = std::strtoull(inicio, &fim, 10);
    return errno != ERANGE && fim == inicio + lexema.size();
}

std::strtoull(_String, _EndPtr, _Radix)

Na tabela abaixo, asserção é uma conferência embutida no código, que interrompe o programa quando uma condição falha.

Parâmetro O que é, com a unidade Padrão Se errar
_String (1º) o texto a converter, terminado em zero obrigatório espaços no começo são pulados, e um sinal - é aceito: "-5" virou 18446744073709551611, sem erro e sem errno. É por isso que o exemplo recusa o sinal antes de chamar
_EndPtr (2º) onde a função grava o endereço do primeiro caractere que ela não usou obrigatório; aceita nullptr, e então não se sabe onde a leitura parou sem conferi-lo, "12abc" vira 12 sem aviso — a leitura parou no a, 2 bytes depois do início; e "abc" vira 0, com o ponteiro devolvido igual ao início e errno intacto
_Radix (3º) a base de numeração, de 2 a 36, ou 0 obrigatório fora dessa faixa, em Debug, o programa é interrompido pela asserção base == 0 \|\| (2 <= base && base <= 36); com 0, a base sai do próprio texto: "0x1F" virou 31 e "017" virou 15, em octal — um literal decimal com zero à esquerda muda de valor em silêncio

Número grande demais não lança exceção, que é o jeito de uma função avisar do erro abandonando o que fazia: std::strtoull termina normalmente e devolve um número. "99999999999999999999999" virou 18446744073709551615, o maior valor do tipo, e deixou em errno o código ERANGE, que quer dizer resultado grande demais. Como std::strtoull deixa errno intacto quando não há erro, o exemplo o zera antes e confere depois.

std::strtod(_String, _EndPtr) tem dois parâmetros, com o mesmo significado dos dois primeiros de std::strtoull, e lê número com parte fracionária. O separador decimal é o ponto: "3,14" virou 3, com a leitura parada na vírgula, um byte depois do início.

No sentido contrário, std::to_string(valor) devolve o texto de um número inteiro ou de ponto flutuante, que é o número com parte fracionária.

Montar a saída

A tabela de nomes (nos manuais, tabela de símbolos) guarda o que cada nome significa. O exemplo monta a listagem dela, com cada nome e o endereço dele em duas colunas alinhadas:

#include <cstddef>
#include <iomanip>
#include <sstream>
#include <string>
#include <vector>

std::string listarSimbolos(const std::vector<std::string>& nomes, const std::vector<int>& enderecos) {
    std::ostringstream saida;
    for (std::size_t i = 0; i < nomes.size() && i < enderecos.size(); ++i) {
        saida << std::left << std::setw(12) << nomes[i]
              << std::right << std::setw(6) << enderecos[i] << '\n';
    }
    return saida.str();
}

std::ostringstream recebe o texto pelo mesmo operador << com que se escreve em std::cout, a saída do programa, acumula-o na memória, e str() o devolve inteiro. Como listarSimbolos devolve o texto em vez de escrevê-lo, o resultado dela serve para a tela, para o arquivo e para o teste que compara a saída com um texto esperado. Na tabela abaixo, número de ponto flutuante é o número com parte fracionária.

Parâmetro O que é, com a unidade Padrão Se errar
std::setw(n) a largura mínima do próximo valor escrito, em bytes, como todo comprimento de std::string (eixos e convenções) 0, sem preenchimento texto acentuado desalinha a coluna. Os colchetes dos resultados desta célula foram escritos em volta de cada campo, para mostrar onde ele começa e termina: com setw(8), ação, de 6 bytes, saiu [ ação], e acao, de 4, saiu [ acao]; na tela, o primeiro ocupa duas posições a menos. Além disso, a largura vale só para o valor seguinte: setw(5) antes de 42 e nada antes do segundo 42 produziu [ 42][42]; valor mais largo que n não é cortado — abcdef em largura 2 saiu inteiro
std::left e std::right de que lado fica o valor dentro da largura std::right ao contrário de setw, permanecem até serem trocados: um std::left esquecido alinha à esquerda todas as colunas numéricas seguintes
std::setprecision(n) quantos algarismos significativos um número de ponto flutuante mostra 6 é algarismo significativo, e não casa decimal: com 3, 3.14159 saiu 3.14 e 1234.5 saiu 1.23e+03
std::hex e std::dec a base dos inteiros escritos depois std::dec permanece: depois de um std::hex, 255 saiu ff duas vezes, e todo número seguinte sai em hexadecimal até um std::dec

std::cout é a saída do programa; std::cerr é a saída de erro. O terminal mostra as duas misturadas, mas elas são canais separados: mandar a saída do programa para um arquivo, em vez da tela, leva só a de std::cout, e as mensagens de erro continuam na tela. Escreva '\n' para quebrar a linha; std::endl quebra e também esvazia a cada chamada o buffer, a área de memória em que a saída fica guardada antes de ser escrita.

Guardar uma sequência

std::vector, o vetor, é uma lista numerada de elementos, e o projeto de referência a usa para guardar estados, transições, símbolos e lexemas. O lexema é o trecho do texto de entrada que forma uma unidade: em total = preco * quantidade, as unidades são cinco, e os lexemas delas são total, =, preco, * e quantidade. Os manuais chamam cada unidade de token. Nos autômatos, o projeto de referência representa cada estado por um índice inteiro, o número do lugar dele no vetor, e não por ponteiro, a variável que guarda o endereço de um dado na memória.

#include <cstddef>
#include <vector>

struct Estado {
    bool aceita;
    std::vector<std::size_t> destinos;
};

std::size_t novoEstado(std::vector<Estado>& estados, bool aceita) {
    estados.push_back(Estado{aceita, {}});
    return estados.size() - 1;
}

void ligar(std::vector<Estado>& estados, std::size_t de, std::size_t para) {
    estados.at(de).destinos.push_back(para);
}

O índice continua valendo quando o vetor cresce, e o ponteiro não. Quando push_back acrescenta um elemento que já não cabe no espaço reservado, o vetor muda os elementos de lugar na memória; a verificação desta página confirmou que o endereço devolvido por data() mudou. Todo ponteiro, referência ou iterador tomado antes passa a apontar para memória liberada. O iterador é o objeto que aponta um elemento do vetor e sabe avançar para o seguinte.

Membro de std::vector O que faz
push_back(valor) acrescenta uma cópia ao fim
emplace_back(argumentos...) constrói o elemento direto no fim, a partir dos argumentos
size() e empty() o número de elementos, e se ele é zero; empty() não esvazia
v[i] o elemento i; em Debug, fora do intervalo interrompe com vector subscript out of range; em Release, não verifica
at(i) o mesmo, com verificação sempre: fora do intervalo lança std::out_of_range com a mensagem invalid vector subscript
back() e pop_back() o último elemento, e a remoção dele; back() num vetor vazio, em Debug, interrompe com back() called on empty vector
clear() remove todos os elementos
begin() e end() os iteradores do intervalo [begin, end)
erase(first, last) remove o intervalo [first, last)

O construtor vector(_Count, _Val) e reserve(_Newcapacity)

Parâmetro O que é, com a unidade Padrão Se errar
_Count (1º do construtor) quantos elementos o vetor já nasce tendo obrigatório confundir com capacidade: um vetor de int construído como v(3) já tem três zeros, e o push_back seguinte cria o quarto elemento, não o primeiro
_Val (2º do construtor) o valor copiado em cada um dos _Count elementos o valor padrão do tipo, com a outra sobrecarga: zero para números a tabela de transições iniciada com 0 em vez de um valor que signifique “sem transição” faz toda célula vazia apontar para o estado 0
_Newcapacity (1º de reserve) quantos elementos cabem antes da próxima mudança de lugar na memória; não muda o tamanho nenhum efeito, se não chamado reserve(10) seguido de v[0] = 1 é acesso fora do intervalo: o tamanho continua zero, e em Debug o programa é interrompido com vector subscript out of range

Mapas e conjuntos

Recipientes (em inglês, containers) são as estruturas da biblioteca que guardam elementos. Os quatro desta seção se distinguem por duas perguntas: guardam par chave–valor ou só a chave, e mantêm ordem ao percorrer ou não. A chave é o dado pelo qual se procura, e o valor é o que fica guardado junto dela.

Recipiente Guarda Ordem ao percorrer
std::map par chave–valor crescente pela chave
std::set só a chave crescente
std::unordered_map par chave–valor nenhuma garantida
std::unordered_set só a chave nenhuma garantida

A diferença de ordem tem consequência em teste. Inseridas as palavras while, if, else, return, for e int, o std::map as percorreu como else for if int return while, e o std::unordered_map como int if while for else return. Uma saída que dependa do percurso de um recipiente sem ordem pode mudar quando se troca a biblioteca padrão, a de um compilador pela de outro, e o teste que compara a saída com um texto fixo deixa de passar sem que nada no seu código tenha mudado.

As palavras reservadas são as que não podem ser nome de variável, e para elas só se pergunta “está ou não está”:

#include <string>
#include <unordered_set>

bool ehPalavraReservada(const std::string& lexema) {
    static const std::unordered_set<std::string> reservadas{"if", "else", "while", "return"};
    return reservadas.contains(lexema);
}

Na construção de subconjuntos, a chave é um conjunto de estados, e o valor é o número do estado novo. std::set pode ser chave de std::map, porque dois std::set podem ser comparados com <, e é com esse < que o std::map mantém as chaves em ordem crescente; não pode ser chave de std::unordered_set nem de std::unordered_map, porque a biblioteca não sabe calcular o hash dele, o número que esses recipientes tiram da chave para decidir onde guardá-la. A tentativa falha com error C2064: term does not evaluate to a function taking 1 arguments. O erro aparece dentro de um cabeçalho da biblioteca, que é um arquivo dela e não seu, com uma cadeia de notas que termina na sua linha.

#include <cstddef>
#include <map>
#include <set>

std::size_t numerar(std::map<std::set<std::size_t>, std::size_t>& numeros, const std::set<std::size_t>& conjunto) {
    if (numeros.contains(conjunto)) {
        return numeros.at(conjunto);
    }
    const std::size_t novo = numeros.size();
    numeros.insert({conjunto, novo});
    return novo;
}
Membro O que faz, e o que acontece quando a chave não existe
contains(chave) se a chave está lá; existe a partir do C++20
count(chave) 0 ou 1; é a forma anterior ao C++20 de perguntar o mesmo
find(chave) um iterador para o elemento, ou end() quando a chave não existe
at(chave) o valor; chave ausente lança std::out_of_range com invalid map<K, T> key, ou invalid unordered_map<K, T> key
m[chave] o valor; chave ausente é criada com o valor padrão do tipo
insert({chave, valor}) insere se a chave não existe; se existe, não altera nada e devolve um par cujo segundo elemento é false
erase(chave) remove, e devolve quantos elementos saíram

O operador [] cria a chave que não existe, e numa tabela de nomes (nos manuais, tabela de símbolos), que guarda o que cada nome do programa significa, isso vira defeito: consultar simbolos["x"] para saber se x foi declarado declara x. Na verificação desta página, ler m["fantasma"] num std::map vazio devolveu 0 e deixou o mapa com tamanho 1. Para perguntar, use contains; para ler sabendo que a chave existe, use at.

A árvore sintática

Na árvore desta seção, o dono de cada nó é o nó pai; a raiz, que não tem pai, pertence a quem a criou. O dono é quem responde por destruir o nó, e essa responsabilidade se chama posse. Um ponteiro é a variável que guarda o endereço de um dado na memória, e std::unique_ptr é o ponteiro da biblioteca que faz o papel de dono: quando o pai é destruído, os filhos vão junto, e nenhum delete, o comando que destrói um objeto, se escreve à mão.

#include <cstddef>
#include <memory>
#include <string>
#include <utility>
#include <vector>

struct No {
    std::string rotulo;
    std::vector<std::unique_ptr<No>> filhos;
};

std::unique_ptr<No> novoNo(const std::string& rotulo) {
    std::unique_ptr<No> no = std::make_unique<No>();
    no->rotulo = rotulo;
    return no;
}

std::unique_ptr<No> binario(const std::string& operador, std::unique_ptr<No> esquerda, std::unique_ptr<No> direita) {
    std::unique_ptr<No> no = novoNo(operador);
    no->filhos.push_back(std::move(esquerda));
    no->filhos.push_back(std::move(direita));
    return no;
}

std::size_t contarNos(const No& raiz) {
    std::size_t total = 1;
    for (const std::unique_ptr<No>& filho : raiz.filhos) {
        total += contarNos(*filho);
    }
    return total;
}
Peça O que faz
std::make_unique cria o nó e devolve o std::unique_ptr que é dono dele; a forma de escrever o tipo do nó está na primeira linha do corpo de novoNo, no exemplo acima; os argumentos de std::make_unique, quando houver, vão entre os parênteses, que ali vêm vazios
std::move(p) autoriza a transferência da posse; depois dela, p fica vazio
p->campo e *p acesso ao nó
p.get() o ponteiro comum, sem posse, para quem só vai olhar
if (p) se há nó; p vazio é falso
p.reset() destrói o nó agora e deixa p vazio

std::unique_ptr não se copia. Passá-lo, guardá-lo ou atribuí-lo sem std::move produz error C2280. A mensagem começa pela linha do seu código e termina em attempting to reference a deleted function. Entre o começo e o fim, ela cita o construtor de cópia do std::unique_ptr que guarda um objeto do tipo declarado por você (no exemplo, No). O construtor de cópia é a função que criaria a cópia, e o cabeçalho memory o declara como excluído (deleted, na mensagem). A transferência de posse, pedida com std::move, é feita pelo construtor de movimento da biblioteca instalada, que passa o nó ao ponteiro novo e deixa vazio o de origem, chamando release() nele: usar esquerda depois de entregá-la por std::move(esquerda) é usar um ponteiro vazio.

Quem só lê a árvore recebe o nó por referência, como contarNos: const No& diz que a função olha e não fica com nada.

Ordenar, procurar, remover

std::sort, std::unique, std::remove_if, std::find e std::find_if, que a biblioteca chama de algoritmos, recebem um intervalo [first, last) de iteradores, e não o recipiente. std::erase_if, que o exemplo usa, funciona de outro jeito: recebe o próprio recipiente. O recipiente é a estrutura que guarda os elementos, como o vetor; o iterador é um objeto que aponta um elemento e sabe avançar para o seguinte. No intervalo, o segundo iterador aponta um depois do último elemento: o colchete diz que first entra, e o parêntese, que last fica de fora.

#include <algorithm>
#include <cstddef>
#include <string>
#include <vector>

void ordenarSemRepetidos(std::vector<std::size_t>& estados) {
    std::sort(estados.begin(), estados.end());
    const std::vector<std::size_t>::iterator novoFim = std::unique(estados.begin(), estados.end());
    estados.erase(novoFim, estados.end());
}

void descartarVazios(std::vector<std::string>& lexemas) {
    std::erase_if(lexemas, [](const std::string& s) -> bool { return s.empty(); });
}

std::sort(_First, _Last, _Pred)

Parâmetro O que é, com a unidade Padrão Se errar
_First (1º), _Last (2º) o intervalo a ordenar, [first, last) obrigatório iteradores de dois vetores diferentes, em Debug, interrompem com vector iterators in range are from different containers
_Pred (3º) a comparação: devolve true quando o primeiro argumento vem estritamente antes do segundo a outra sobrecarga, sem ele, usa < escrita com <=, ela diz que um elemento vem antes de si mesmo; em Debug, a ordenação é interrompida com invalid comparator, e em Release o resultado é comportamento indefinido

std::unique(_First, _Last) e std::remove_if(_First, _Last, _Pred) não removem nada. Eles levam para o começo do intervalo os elementos que ficam e devolvem o iterador do ponto em que começa o que deveria sair; o tamanho do vetor não muda até o erase. O cabeçalho da biblioteca instalada marca os dois: chamá-los sem usar o valor devolvido produz C4858, com uma mensagem que manda chamar container.erase(result, container.end()) em seguida. std::unique só trata como repetidos os iguais que estão lado a lado, e é por isso que o exemplo ordena antes.

O texto do C4858 de remove e remove_if, e não o de unique, termina apontando std::erase e std::erase_if, do C++20, como a forma de uma chamada só: In C++20, 'std::erase' and 'std::erase_if' are simpler replacements for these two steps. std::erase_if(v, condição) remove e encolhe o vetor, e devolve quantos elementos saíram.

Peça O que faz
std::find(first, last, valor) iterador para o primeiro elemento igual a valor, ou last se não há
std::find_if(first, last, condição) iterador para o primeiro elemento que satisfaz a condição, ou last

No exemplo, a condição passada a erase_if é uma função lambda, uma função sem nome escrita no ponto em que é usada: [](const std::string& s) -> bool { return s.empty(); } recebe um elemento e devolve bool. find_if e remove_if recebem uma condição da mesma forma. Escreva o tipo do parâmetro e o tipo de retorno; é o que o compilador de C++ usa para dizer, na mensagem de erro, o que ele esperava receber.

A função main

O exemplo espera exatamente um argumento, o nome de um arquivo escrito depois do nome do programa; com outro número de argumentos, escreve a forma de uso na saída de erro e sai com falha:

#include <cstdlib>
#include <iostream>
#include <string>

int main(int argc, char** argv) {
    if (argc != 2) {
        std::cerr << "uso: " << argv[0] << " <arquivo>\n";
        return EXIT_FAILURE;
    }
    const std::string caminho{argv[1]};
    std::cout << "lendo " << caminho << '\n';
    return EXIT_SUCCESS;
}
Parâmetro O que é, com a unidade Padrão Se errar
argc quantos elementos argv tem, contando o nome do programa obrigatório testar argc != 1 para “recebeu um argumento” rejeita justamente a chamada certa: com um argumento, argc vale 2
argv os argumentos, como textos terminados em zero; argv[0] é o nome do programa e argv[1] o primeiro argumento obrigatório ler argv[1] sem conferir argc é acesso fora do arranjo quando o programa é chamado sem argumento
o valor de return o código de saída, devolvido ao sistema e ao CTest 0, se a execução chegar ao fim de main sem return devolver 0 depois de o seu compilador achar erro no texto de entrada faz o teste daquele caso passar

EXIT_SUCCESS e EXIT_FAILURE, do cabeçalho cstdlib, valem 0 e 1 nesta biblioteca. Um compilador que recusa a entrada deve sair com código diferente de zero: é o que permite a um teste confirmar que uma entrada inválida foi recusada, desde que esse teste tenha o veredito invertido pela propriedade WILL_FAIL do CTest, o programa que roda os testes (transformar uma execução em teste).

Chamar outro programa

Se o seu compilador gera código em C para outro compilador traduzir, ele precisa chamá-lo. std::system entrega uma linha de comando ao interpretador de comandos do sistema, o programa que executa o que se digita no terminal, e espera o comando terminar. O exemplo pergunta antes se há interpretador e devolve -1 quando não há.

#include <cstdlib>
#include <string>

int compilarGerado(const std::string& comando) {
    if (std::system(nullptr) == 0) {
        return -1;
    }
    return std::system(comando.c_str());
}

std::system(_Command)

Parâmetro O que é, com a unidade Padrão Se errar
_Command (1º) a linha de comando, como texto terminado em zero; nullptr pergunta se há interpretador de comandos disponível obrigatório a linha é interpretada pelo interpretador do sistema, com a sintaxe dele: redirecionamento, descarte de saída e separador de caminho escritos para um sistema falham no outro

O valor devolvido, nesta biblioteca, foi o código de saída do comando, o número que um programa devolve ao terminar: std::system("exit 3") devolveu 3, e std::system(nullptr) devolveu 1, dizendo que há interpretador. Em outras bibliotecas o formato desse retorno pode ser outro, e esta página não o conferiu.

Quando o seu programa chama outro, o seu teste passa a depender de algo instalado fora do projeto, e esse teste precisa decidir o que faz quando o programa chamado falta na máquina. Se o teste apenas escreve uma mensagem e sai com 0, a falta fica escondida. Quem roda os testes é o CTest. Ele decide pelo código de saída, o número que o seu programa devolve ao terminar (transformar uma execução em teste), e marca como aprovado um teste que não conferiu nada. E a opção --output-on-failure do ctest não mostra aquela mensagem, porque só mostra a saída dos testes que falharam.

O remédio é a propriedade de teste SKIP_RETURN_CODE, que existe no CTest desde a versão 3.0. O seu programa sai com um código reservado para “não pude conferir”, entre 0 e 255, e o teste que declara esse código aparece como pulado, e não como aprovado. Para um teste de nome compila_gerado cujo programa sai com 77 quando falta o outro compilador, a linha do arquivo de build é set_tests_properties(compila_gerado PROPERTIES SKIP_RETURN_CODE 77). Na verificação desta página, a linha desse teste na saída do ctest terminou em ***Skipped, e o resumo do ctest trouxe, depois dos resultados, The following tests did not run: com compila_gerado (Skipped) logo abaixo. Um teste pulado não conta como falha: com os demais aprovados, o ctest informou 100% tests passed e saiu com 0. A diferença é que a falta aparece na saída, em vez de se esconder atrás de um Passed.

Quando não funciona

Sintoma Causa provável Onde confirmar
No CMAKE_CXX_COMPILER could be found. o CMake foi rodado fora do ambiente de desenvolvimento do MSVC construir e testar pela linha de comando
does not appear to contain CMakeLists.txt -S aponta a pasta errada construir e testar pela linha de comando
Does not match the generator used previously gerador diferente do usado na primeira vez que aquela pasta de build foi configurada construir e testar pela linha de comando
is not able to compile a simple test program, com devenv.com e no such file or directory logo abaixo gerador do Visual Studio, o padrão quando falta -G, numa máquina que tem só as ferramentas de build do Visual Studio construir e testar pela linha de comando
error MSB8020: The build tools for ... cannot be found arquivo de presets de outra máquina, pedindo um conjunto de ferramentas que esta não tem construir pelo editor
aviso do CMake que começa por The object file directory e cita o máximo de caracteres, 250 na máquina desta página o projeto está numa pasta profunda demais; o caminho de cada objeto passa do limite construir e testar pela linha de comando
Command line warning D9025 : overriding '/W3' with '/W4' em toda compilação cmake_minimum_required abaixo de 3.15 deixa a política CMP0092 no comportamento antigo, que põe /W3 nas opções padrão; o remédio é subir o mínimo para 3.15 ou mais o arquivo de build
LNK2019: unresolved external symbol função declarada e chamada, cujo .cpp não está em add_executable o arquivo de build
error C2220: the following warning is treated as an error há um aviso, e /WX o transformou em erro; a causa é o aviso impresso logo depois da linha do C2220 o modo estrito do compilador
C4267 ... conversion from 'size_t' to 'int' tamanho ou posição guardado em int o modo estrito do compilador
C2280 ... attempting to reference a deleted function com unique_ptr cópia de std::unique_ptr sem std::move a árvore sintática
C2064: term does not evaluate to a function taking 1 arguments dentro de um cabeçalho da biblioteca chave sem hash num recipiente unordered_ mapas e conjuntos
C4858: discarding return value empty() no lugar de clear(), ou unique/remove_if sem erase o modo estrito do compilador
C2039: 'starts_with': is not a member of o padrão C++20 não chegou ao alvo o arquivo de build
Test not available without configuration falta -C Debug no ctest, em gerador de várias configurações de build construir e testar pela linha de comando
No tests were found!!! pasta de build errada, ou -R que não casa nome nenhum construir e testar pela linha de comando
No tests were found!!! enable_testing() ausente transformar uma execução em teste
o teste não acha o arquivo que o programa acha quando rodado à mão falta WORKING_DIRECTORY transformar uma execução em teste
o teste passa, e a saída mostra um erro o programa escreve o erro e sai com 0 a função main
o teste de uma entrada inválida sai ***Failed, e o seu compilador a recusou como devia falta WILL_FAIL no teste dessa entrada transformar uma execução em teste
o teste que chama outro programa passa numa máquina em que esse programa não existe o teste sai com 0 quando o programa falta, em vez de sair com o código declarado em SKIP_RETURN_CODE, que o marca como pulado chamar outro programa
--config Release e nada muda gerador de uma configuração de build só; nele, a configuração de build se escolhe na etapa de configuração construir e testar pela linha de comando
o analisador recusa um símbolo invisível no fim de cada linha \r do fim de linha do Windows, em leitura binária ler um arquivo
a coluna do erro fica deslocada para a direita acento na linha: a coluna conta bytes andar pelo texto
Assertion failed: c >= -1 && c <= 255 char negativo passado a isalpha e afins classificar um caractere
vector subscript out of range índice fora do vetor, em Debug; em Release o mesmo defeito não dá mensagem guardar uma sequência
invalid string position substr ou at além do fim do texto andar pelo texto
invalid comparator comparação com <= passada a std::sort ordenar, procurar, remover
o laço de busca não termina find sem o segundo argumento, recomeçando do início andar pelo texto
número negativo lido como número enorme std::strtoull aceita - e dá a volta no tipo converter texto em número
3,14 lido como 3 o separador decimal é o ponto converter texto em número
a tabela de nomes (nos manuais, tabela de símbolos) ganha nomes que ninguém declarou consulta com [], que cria a chave mapas e conjuntos
a ordem da saída muda sem mudança no código percurso de recipiente unordered_ mapas e conjuntos
ponteiro para estado aponta lixo depois de criar outro estado o vetor mudou de lugar ao crescer guardar uma sequência
gravar falha para um arquivo sem pasta no nome create_directories com caminho vazio gravar um arquivo
todas as colunas da listagem alinhadas à esquerda std::left permanece até ser trocado montar a saída
a coluna da listagem desalinha nas linhas com acento std::setw conta bytes, e letra acentuada ocupa dois montar a saída

Diante de qualquer sintoma, pergunte primeiro em que etapa ele apareceu. CMake Error é da etapa de configuração, em que o CMake lê o arquivo de build, e nenhuma mudança no .cpp o conserta. Código que começa por C, como C2220, é do compilador de C++, e aponta arquivo e linha. Código que começa por LNK é do ligador, o programa que junta num executável o que foi compilado, e aponta um nome, e não uma linha. Mensagem que surge com o programa já rodando é do seu programa ou da biblioteca: ela só vem depois de tudo o mais ter passado.

Glossário

Termo O que é
alvo (target) aquilo que o CMake sabe construir, com seus .cpp e suas opções
ambiente de desenvolvimento (Native Tools Command Prompt, no nome do atalho) o terminal do MSVC em que o cl e o ligador são encontrados pelo PATH, a lista de pastas em que o terminal procura os programas, e as variáveis de ambiente INCLUDE e LIB vêm preenchidas; fora dele, o CMake com o Ninja Multi-Config responde No CMAKE_CXX_COMPILER could be found.
arquivo de build (CMakeLists.txt) o arquivo que diz ao CMake o que construir e com que opções; o de topo fica na raiz do projeto
arquivo-objeto (object file) o .obj que o compilador de C++ produz de cada unidade de tradução, e que o ligador junta num executável
asserção (assertion) conferência embutida no código, que interrompe o programa quando uma condição falha; na biblioteca da Microsoft, ativa em Debug
assinatura o nome de uma função seguido dos parâmetros que ela recebe, na ordem
aviso (warning) mensagem do compilador de C++ que não impede a compilação; sob /WX, todo aviso vira erro
buffer a área de memória em que a saída fica guardada antes de ser escrita
cabeçalho (header) arquivo .h com declarações, colado por #include em cada unidade de tradução
código de saída (exit code) o número que o programa devolve ao sistema ao terminar; 0 é sucesso
comportamento indefinido (undefined behavior) o que acontece quando o programa faz algo incorreto para o qual a linguagem não exige nada, nem que o compilador avise; o programa pode funcionar, travar ou dar resultado errado
comprimento, ou tamanho uma quantidade de elementos; num std::string, medida em bytes
configuração de build (build configuration) Debug, Release ou outra; decide quanto o compilador otimiza e o que a biblioteca confere enquanto o programa roda; não é a etapa de configuração
construir (build) a etapa em que a ferramenta de construção chama o compilador de C++ e o ligador; seus erros têm código C ou LNK
declaração (declaration) diz que um nome existe e qual é o tipo dele, sem entregar o corpo
definição (definition) entrega o corpo, as instruções que a função executa; uma função pode ser declarada em muitos lugares e definida em um só
diretório de trabalho (working directory) a pasta de onde o programa é executado, e a partir da qual ele procura um caminho relativo como exemplos/entrada.txt. No arquivo de build, os .cpp listados no alvo partem da pasta desse arquivo; já um caminho relativo passado ao programa por add_test é procurado a partir do diretório de trabalho do teste, que sem WORKING_DIRECTORY é a pasta de build
erro (error) mensagem do compilador de C++ que impede a compilação
etapa de configuração (configure) a etapa em que o CMake lê o arquivo de build, descobre o compilador de C++ e gera os arquivos da ferramenta de construção; seus erros começam por CMake Error; não é a configuração de build
exceção (exception) o jeito de uma função avisar do erro abandonando o que fazia
extensão de compilador (nonstandard extension, nos avisos do MSVC) construção aceita por um compilador e fora do padrão da linguagem
ferramenta de construção (build tool, na documentação do CMake) o programa que chama o compilador de C++ e o ligador, como o Ninja; o CMake gera os arquivos que ela lê
função lambda uma função sem nome escrita no ponto em que é usada
gerador (generator) a parte do CMake que escreve os arquivos de uma ferramenta de construção; cada gerador tem um nome, como Ninja Multi-Config, e a escolha dele decide que ferramenta vai construir
iterador (iterator) objeto que aponta um elemento de um recipiente e sabe avançar para o seguinte
kit (kit, na extensão CMake Tools) conjunto nomeado que diz à extensão CMake Tools com que compilador configurar, quando ela não usa preset
lexema o trecho do texto de entrada que forma uma unidade, como total ou =; os manuais chamam cada unidade de token
ligador (linker) o programa que junta os arquivos-objeto num executável; seus erros começam por LNK
modo estrito o conjunto de opções de compilação que o projeto liga; para o MSVC, /W4 /WX /permissive- /utf-8 /EHsc
padrão da linguagem (standard) a norma que define o C++; o C++20 é uma versão dela. Não é o valor assumido de um parâmetro omitido, em inglês default
política (policy) regra de compatibilidade do CMake, ligada à versão declarada em cmake_minimum_required
posição o lugar de um elemento numa sequência, contado a partir de zero; num std::string, o lugar de um byte
posse (ownership) a responsabilidade de destruir um objeto; num std::unique_ptr, ela é de um dono só
preset conjunto nomeado de opções do CMake, guardado num arquivo do projeto
recipiente (container) estrutura da biblioteca que guarda elementos: vector, map, set e afins
unidade de tradução (translation unit) um .cpp depois de todos os #include colados; o que o compilador de C++ processa de uma vez
UTF-8 a codificação em que letra sem acento ocupa um byte e letra acentuada do português ocupa dois
variante (variant, na extensão CMake Tools) conjunto de opções da etapa de configuração, com que a extensão configura quando não usa preset; a principal dessas opções é a configuração de build