C++ std::format e std::print
Na aula anterior, você montou a tabela de notas da Ana com std::cout, std::setw, std::left, std::fixed… e muitos <<. Funciona, mas é comprido e as configurações "grudam". O C++ moderno trouxe um jeito mais curto, inspirado em Python: você escreve o texto com buracos marcados por {}, e o C++ preenche cada buraco com um valor. Isso é o std::format C++20. E o std::println C++23 faz o mesmo e já mostra na tela, pulando a linha.
- Como o
{}funciona e a diferença entrestd::format,std::printestd::println. - Casas decimais (
{:.2f}), largura e alinhamento ({:>10}), zeros à esquerda ({:05}), hexadecimal ({:x}). - Argumentos numerados (
{0},{1}) e como escrever uma chave de verdade ({{). - A mesma tabela de notas com
cout+iomanipe comprintln, lado a lado. - Como mostrar
1.234,50no formato brasileiro, com{:L}e o locale. - Por que um erro no texto de formato aparece já na compilação, e qual
-std=cada recurso exige.
Todos os programas desta aula foram compilados com o g++ 15.2 na máquina fictícia da Ana (usuária ana, computador notebook), com g++ -std=c++23 -Wall -Wextra arquivo.cpp -o programa. Aqui o -std=c++23 é obrigatório (seção 6).
A ideia: o formulário com lacunas
Pense num formulário impresso: "Eu, ______, com ______ anos, declaro…". Você não reescreve o formulário inteiro para cada pessoa: só preenche as lacunas. No std::format, o texto com lacunas se chama string de formato, e cada lacuna é um par de chaves {}:
| Jeito | Código |
|---|---|
cout | std::cout << nome << " tem " << idade << " anos.\n"; |
format | std::cout << std::format("{} tem {} anos.\n", nome, idade); |
println | std::println("{} tem {} anos.", nome, idade); |
As três linhas mostram a mesma coisa. Com as lacunas, você enxerga a frase inteira de uma vez, sem picotá-la em pedaços.
1. std::format: o {}
#include <format>
#include <iostream>
#include <string>
int main() {
std::string nome = "Ana";
int idade = 29;
std::string frase = std::format("{} tem {} anos.", nome, idade);
std::cout << frase << "\n";
}
$ g++ -std=c++23 -Wall -Wextra ola.cpp -o ola $ ./ola Ana tem 29 anos.
- #include <format>
- Traz o
std::format(C++20). - "{} tem {} anos."
- A string de formato. Cada
{}é uma lacuna. Tudo o mais é copiado como está. - nome, idade
- Os argumentos, os valores que preenchem as lacunas, na ordem: o primeiro
{}recebenome, o segundo recebeidade. - std::string frase = …
- O
std::formatnão mostra nada: ele devolve um texto (std::string, aula Strings) pronto. Você pode guardá-lo, juntá-lo com outros, gravá-lo num arquivo, ou mostrá-lo comcout.
Qualquer tipo básico entra numa lacuna: textos, inteiros, double, char, e até bool, que sai como true/false (sem precisar de boolalpha).
2. std::print e std::println: formatar e mostrar C++23
Como quase sempre o objetivo é mostrar o texto na tela, o C++23 juntou as duas coisas:
#include <print>
int main() {
std::print("Olá, ");
std::print("{}!\n", "Ana");
std::println("Total: R$ {:.2f}", 4.5 * 3);
std::println("{:<8}{:>6.1f}", "Bruno", 7.0);
}
$ g++ -std=c++23 -Wall -Wextra println.cpp -o println $ ./println Olá, Ana! « dois print na mesma linha: o print não pula linha Total: R$ 13.50 Bruno 7.0
- #include <print>
- Traz
std::printestd::println. Repare que nem precisou de<iostream>. - std::print(...)
- Formata e mostra. Não pula linha: você põe o
\nse quiser. - std::println(...)
- print line, "mostre a linha": formata, mostra e pula linha no final.
| Função | Cabeçalho | Mostra na tela? | Pula linha? | Versão |
|---|---|---|---|---|
std::format | <format> | Não, devolve um std::string | Só com \n | C++20 |
std::print | <print> | Sim | Só com \n | C++23 |
std::println | <print> | Sim | Sim, sozinho | C++23 |
Missão: complete o println para mostrar Ana tem 29 anos e 1.65 m de altura., usando as três variáveis. A altura deve sair com duas casas (veja a seção 3, ou use a dica: {:.2f}).
#include <print>
#include <string>
int main() {
std::string nome = "Ana";
int idade = 29;
double altura = 1.6489;
std::println("...");
}
#include <print>
#include <string>
int main() {
std::string nome = "Ana";
int idade = 29;
double altura = 1.6489;
std::println("{} tem {} anos e {:.2f} m de altura.", nome, idade, altura);
}
3. Dentro das chaves: as especificações
Uma lacuna pode levar instruções de como mostrar o valor. Elas vêm depois de dois-pontos: {:especificação}. É o mesmo trabalho dos manipuladores da aula anterior, numa linguagem curtinha:
#include <format>
#include <iostream>
int main() {
std::cout << std::format("Preço: R$ {:.2f}", 19.9) << "\n";
std::cout << std::format("[{:>10}]", "Ana") << "\n";
std::cout << std::format("[{:<10}]", "Ana") << "\n";
std::cout << std::format("[{:^10}]", "Ana") << "\n";
std::cout << std::format("[{:*^10}]", "Ana") << "\n";
std::cout << std::format("[{:05}]", 42) << "\n";
std::cout << std::format("{:x} {:X} {:#x} {:b}", 255, 255, 255, 5) << "\n";
}
$ ./basico
Preço: R$ 19.90 « {:.2f}: duas casas
[ Ana] « {:>10}: largura 10, à direita
[Ana ] « {:<10}: à esquerda
[ Ana ] « {:^10}: centralizado
[***Ana****] « {:*^10}: centralizado, preenchendo com *
[00042] « {:05}: largura 5 com zeros
ff FF 0xff 101 « hexadecimal, maiúsculo, com 0x, e binário
A especificação segue sempre a mesma ordem. Você só escreve as partes de que precisa:
| Parte | Exemplo | O que faz |
|---|---|---|
| preenchimento + alinhamento | *^, <, >, ^ | < esquerda, > direita, ^ centro. Um caractere antes do sinal é o preenchimento (padrão: espaço). |
| sinal | + | Mostra + também nos positivos: {:+} com 5 → +5. |
# | #x | Mostra o prefixo da base (0x, 0b). |
0 + largura | 05 | Completa com zeros à esquerda (só para números). |
| largura | 10 | Largura mínima do campo. |
. precisão | .2 | Com f: casas decimais. Num texto: corta no tamanho. |
| tipo | f, e, x, X, b, o, d | f ponto fixo, e notação científica, x hexadecimal, b binário, o octal, d decimal. |
Exemplo completo: {:*>10.2f} = "preencha com *, alinhe à direita, largura 10, duas casas, ponto fixo". Sem alinhamento, os textos vão à esquerda e os números à direita: {:10} com "Ana" dá [Ana ], e com 42 dá [ 42].
Cada lacuna tem a sua especificação, que vale só para ela. Não existe o problema da aula de iomanip, em que um std::fixed esquecido muda o resto do programa.
4. Argumentos numerados e chaves de verdade
Dentro das chaves, antes dos dois-pontos, você pode dizer qual argumento usar, começando do zero. Assim dá para repetir um valor ou trocar a ordem:
#include <format>
#include <iostream>
int main() {
std::cout << std::format("{0} e {1}; {1} e {0}", "Ana", "Bruno") << "\n";
std::cout << std::format("{{chaves}} {}", 1) << "\n";
}
$ ./numerados
Ana e Bruno; Bruno e Ana
{chaves} 1 « {{ vira {, e }} vira }
- {0}, {1}
- O argumento número 0 (
"Ana") e o número 1 ("Bruno"). Com número e especificação:{1:>8}. - {{ e }}
- Para escrever uma chave de verdade no texto, dobre-a.
Não dá para misturar os dois estilos na mesma string: ou todas as lacunas têm número, ou nenhuma tem.
5. Comparação: a mesma tabela com cout e com println
Esta é a tabela de notas da aula de iomanip, feita dos dois jeitos. As saídas são idênticas:
cout + <iomanip> | std::println |
|---|---|
std::cout << std::left << std::setw(10) << "Aluno"
<< std::right << std::setw(8) << "Nota" << "\n";
std::cout << std::setfill('-') << std::setw(18) << ""
<< std::setfill(' ') << "\n";
std::cout << std::fixed << std::setprecision(1);
std::cout << std::left << std::setw(10) << "Ana"
<< std::right << std::setw(8) << 9.5 << "\n"; |
std::println("{:<10}{:>8}", "Aluno", "Nota");
std::println("{:-<18}", "");
std::println("{:<10}{:>8.1f}", "Ana", 9.5);
std::println("{:<10}{:>8.1f}", "Bruno", 7.25);
std::println("{:<10}{:>8.1f}", "Carla", 10.0); |
$ g++ -std=c++23 -Wall -Wextra tabela.cpp -o tabela $ ./tabela Aluno Nota ------------------ Ana 9.5 Bruno 7.2 « como no cout: empate exato vai para o par Carla 10.0
Cinco linhas curtas contra mais de dez, e o formato de cada linha fica visível de uma vez: {:<10}{:>8.1f} é "nome à esquerda em 10, nota à direita em 8 com uma casa". Outra vantagem: o std::format mede a largura pela aparência das letras, e não pelos bytes. Os nomes com acento, que entortavam a coluna do setw, ficam alinhados:
std::cout << std::format("{:<10}|", "Ana") << "\n";
std::cout << std::format("{:<10}|", "João") << "\n";
std::cout << std::format("{:<10}|", "Lúcia Inês") << "\n";
$ ./largura Ana | João | « alinhado (com setw, faltava um espaço) Lúcia Inês|
Qual usar
| Situação | Use |
|---|---|
| Código novo, compilador moderno (g++ 14 ou mais novo, Clang 18 ou mais novo, Visual Studio 2022 versão 17.7 ou mais nova) | std::println / std::print |
| Precisa do texto numa variável (para gravar num arquivo, juntar, devolver de uma função) | std::format |
Compilador preso ao C++17 ou mais antigo, ou mexendo em código que já usa cout | cout + <iomanip> |
| Coisas simples, sem formatação | Tanto faz: std::cout << x continua ótimo |
Você pode misturar cout e println no mesmo programa; as saídas aparecem na ordem certa.
6. Versões: o -std=c++23 é obrigatório
O g++ 15 compila como C++17 se você não pedir outra versão. O std::format é do C++20 e o std::print, do C++23. Veja o que acontece sem o -std=:
$ g++ -Wall -Wextra ola.cpp -o ola
ola.cpp: In function ‘int main()’:
ola.cpp:8:30: error: ‘format’ is not a member of ‘std’
8 | std::string frase = std::format("{} tem {} anos.", nome, idade);
| ^~~~~~
ola.cpp:8:30: note: ‘std::format’ is only available from C++20 onwards
E com -std=c++20, o format passa, mas o print não:
$ g++ -std=c++20 -Wall -Wextra println.cpp -o println
println.cpp: In function ‘int main()’:
println.cpp:4:10: error: ‘print’ is not a member of ‘std’
4 | std::print("Olá, ");
| ^~~~~
println.cpp:4:10: note: ‘std::print’ is only available from C++23 onwards
println.cpp:5:10: error: ‘print’ is not a member of ‘std’
5 | std::print("{}!\n", "Ana");
| ^~~~~
println.cpp:5:10: note: ‘std::print’ is only available from C++23 onwards
println.cpp:6:10: error: ‘println’ is not a member of ‘std’
6 | std::println("Total: R$ {:.2f}", 4.5 * 3);
| ^~~~~~~
println.cpp:6:10: note: ‘std::println’ is only available from C++23 onwards
println.cpp:7:10: error: ‘println’ is not a member of ‘std’
7 | std::println("{:<8}{:>6.1f}", "Bruno", 7.0);
| ^~~~~~~
println.cpp:7:10: note: ‘std::println’ is only available from C++23 onwards
As notas são claras: "std::print só está disponível a partir do C++23". Por isso este guia usa sempre -std=c++23 (aula Compilar com g++). O editor do guia já compila com -std=c++23. Para conhecer as versões, veja Versões do C++.
7. O formato brasileiro: {:L} e o locale
O std::format sempre usa ponto decimal e nenhum separador de milhar: 1234567.89. Isso é proposital: o resultado é o mesmo em qualquer computador, o que é ótimo para arquivos e dados. Para mostrar ao usuário brasileiro 1.234.567,89, você precisa de duas coisas:
- a letra
Lna especificação ({:L},{:.2Lf}), que quer dizer "use as regras do locale"; - um locale brasileiro. Locale ("local") é o pacote de costumes de um lugar: vírgula ou ponto, separador de milhar, formato de data. O do Brasil se chama
pt_BR.UTF-8.
#include <format>
#include <iostream>
#include <locale>
int main() {
double v = 1234567.891;
std::cout << std::format("{:.2f}", v) << "\n";
std::cout << std::format("{:L}", v) << "\n";
std::cout << std::format(std::locale("pt_BR.UTF-8"), "{:.2Lf}", v) << "\n";
std::cout << std::format(std::locale("pt_BR.UTF-8"), "{:L}", 1234567) << "\n";
}
$ g++ -std=c++23 -Wall -Wextra local.cpp -o local $ ./local 1234567.89 « sem L: ponto, sem separador 1234567.891 « L sozinho: o locale padrão do programa é o "C", americano 1.234.567,89 « L + locale pt_BR: formato brasileiro 1.234.567
- #include <locale>
- Traz o
std::locale. - std::locale("pt_BR.UTF-8")
- Carrega as regras brasileiras do sistema operacional.
- std::format(locale, "...", v)
- Quando o primeiro argumento é um locale, as lacunas com
Lseguem as regras dele. - {:L} sem locale
- Repare na segunda linha: mesmo com o Linux da Ana em português, o programa começa no locale "C" (o "neutro", americano). O
Lnão mudou nada.
Para não repetir o locale em toda chamada, você pode trocar o locale do programa inteiro no começo do main. O std::locale("") (texto vazio) quer dizer "o locale de quem está usando o computador", lido da variável LANG (pt_BR.UTF-8 na máquina da Ana):
std::locale::global(std::locale(""));
std::cout << std::format("{:L}", 1234.5) << "\n";
std::cout << std::format("R$ {:.2Lf}", 1234.5) << "\n";
std::cout << std::format("{:.2f}", 1234.5) << "\n";
$ ./global 1.234,5 R$ 1.234,50 1234.50 « sem o L, continua com ponto
Os locales vêm do sistema operacional. Se o pt_BR.UTF-8 não estiver instalado (é comum em servidores e contêineres), o programa compila, mas morre ao rodar. Com um nome inexistente:
$ ./semlocale; echo $? « usa std::locale("xx_XX.UTF-8")
terminate called after throwing an instance of 'std::runtime_error'
what(): locale::facet::_S_create_c_locale name not valid
134
"O nome do locale não é válido". Isso é uma exceção (aula Exceções) que ninguém tratou, e o programa foi encerrado. No Ubuntu, o locale brasileiro vem com o pacote language-pack-pt. O editor do guia roda no navegador, sem os locales do sistema: lá, rode só os exemplos sem std::locale.
8. Erros de formato aparecem na compilação
Aqui o std::format tem uma vantagem enorme sobre o printf do C: o compilador lê a string de formato e confere se ela combina com os argumentos. Se não combinar, o programa nem chega a ser criado. Três erros comuns:
#include <format>
#include <iostream>
int main() {
std::cout << std::format("{} tem {} anos", "Ana") << "\n";
}
$ g++ -std=c++23 -Wall -Wextra erro2.cpp -o erro2
erro2.cpp: In function ‘int main()’:
erro2.cpp:5:30: error: call to consteval function ‘std::basic_format_string<char, const char (&)[4]>("{} tem {} anos")’ is not a constant expression
5 | std::cout << std::format("{} tem {} anos", "Ana") << "\n";
| ^~~~~~~~~~~~~~~~
In file included from erro2.cpp:1:
erro2.cpp:5:30: in ‘constexpr’ expansion of ‘std::basic_format_string<char, const char (&)[4]>("{} tem {} anos")’
/usr/include/c++/15/format:4810:19: in ‘constexpr’ expansion of ‘__scanner.std::__format::_Checking_scanner<char, char [4]>::std::__format::_Scanner<char>.std::__format::_Scanner<char>::_M_scan()’
/usr/include/c++/15/format:4504:30: in ‘constexpr’ expansion of ‘((std::__format::_Scanner<char>*)this)->std::__format::_Scanner<char>::_M_on_replacement_field()’
/usr/include/c++/15/format:4537:28: in ‘constexpr’ expansion of ‘((std::__format::_Scanner<char>*)this)->std::__format::_Scanner<char>::_M_pc.std::__format::_Scanner<char>::_Parse_context::std::basic_format_parse_context<char>.std::basic_format_parse_context<char>::next_arg_id()’
/usr/include/c++/15/format:313:56: error: call to non-‘constexpr’ function ‘void std::__format::__invalid_arg_id_in_format_string()’
313 | __format::__invalid_arg_id_in_format_string();
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~
/usr/include/c++/15/format:259:3: note: ‘void std::__format::__invalid_arg_id_in_format_string()’ declared here
259 | __invalid_arg_id_in_format_string()
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Assusta, mas só duas partes importam:
- erro2.cpp:5:30: error: call to consteval function … is not a constant expression
- A linha do problema (5) e o aviso de que a conferência da string de formato, feita durante a compilação, falhou. Consteval = "avaliado na compilação".
- __invalid_arg_id_in_format_string
- O motivo, escondido no nome de uma função interna da biblioteca: "número de argumento inválido na string de formato". A string tem dois
{}, mas só um argumento foi passado.
Os outros dois erros comuns têm a mesma primeira linha, e o motivo aparece em outro nome de função:
| Código errado | Nome que aparece no erro | Tradução |
|---|---|---|
std::format("{} tem {} anos", "Ana") | __invalid_arg_id_in_format_string | Mais lacunas do que argumentos. |
std::format("Total: {:.2f", 9.5) | __unmatched_left_brace_in_format_string | Uma { sem a } que a fecha. |
std::format("Nota: {:.1f}", "dez") | __failed_to_parse_format_spec | A especificação não serve para esse tipo: f é para números, e "dez" é texto. |
No editor do guia (Clang), a primeira linha diz o mesmo (call to consteval function … is not a constant expression). O motivo aparece nas linhas note: logo abaixo, com outras palavras, porque o editor usa outra biblioteca padrão (a libc++, do projeto LLVM).
Para poder conferir, o compilador precisa ver a string. Guardá-la numa variável não funciona:
$ g++ -std=c++23 -Wall -Wextra var.cpp -o var « std::string modelo = "Olá, {}!"; std::format(modelo, "Ana")
var.cpp: In function ‘int main()’:
var.cpp:7:30: error: call to consteval function ‘std::basic_format_string<char, const char (&)[4]>(modelo)’ is not a constant expression
7 | std::cout << std::format(modelo, "Ana") << "\n";
| ^~~~~~
Escreva o texto entre aspas direto na chamada. (Para os raros casos em que o formato só é conhecido com o programa rodando, existe o std::vformat, que confere durante a execução; você não vai precisar dele tão cedo.)
Missão: este programa não compila. Rode, leia o erro e conserte a string de formato para mostrar Total: R$ 9.50.
#include <print>
int main() {
double total = 9.5;
std::println("Total: R$ {:.2f", total);
}
#include <print>
int main() {
double total = 9.5;
std::println("Total: R$ {:.2f}", total);
}
Quando dá errado
error: ‘format’ is not a member of ‘std’ / ‘print’ is not a member of ‘std’
O que significa: há duas causas. Se a nota diz only available from C++20 onwards (ou C++23), faltou o -std=c++23 no comando. Se não diz, faltou o #include <format> ou #include <print>.
Como consertar: compile com g++ -std=c++23 -Wall -Wextra … e confira os #include.
error: call to consteval function … is not a constant expression
O que significa: a string de formato tem um problema: lacunas a mais, chave sem par, especificação que não serve para o tipo, ou não está escrita direto na chamada.
Como consertar: procure, mais abaixo na mensagem, o nome com __ (como __invalid_arg_id_in_format_string) e use a tabela da seção 8. Conte as lacunas e os argumentos.
terminate called after throwing an instance of 'std::runtime_error' … name not valid
O que significa: o locale pedido (por exemplo, pt_BR.UTF-8) não está instalado no computador onde o programa rodou.
Como consertar: instale o locale (no Ubuntu, sudo apt install language-pack-pt) ou não dependa dele: mostre com ponto, ou troque o ponto pela vírgula no texto pronto.
{} na tela em vez do valor
O que significa: você usou std::cout << "Olá, {}", sem std::format. O cout não entende lacunas: mostra o texto como está.
Como consertar: std::cout << std::format("Olá, {}", nome) ou std::println("Olá, {}", nome).
Referência rápida
| Escrita | Valor | Resultado |
|---|---|---|
{} | "Ana", 42, true | Ana, 42, true |
{:.2f} | 19.9 | 19.90 |
{:>10} / {:<10} / {:^10} | "Ana" | Ana / Ana / Ana |
{:*^10} | "Ana" | ***Ana**** |
{:05} | 42 | 00042 |
{:x} / {:X} / {:#x} | 255 | ff / FF / 0xff |
{:b} | 5 | 101 |
{:+} | 5 | +5 |
{:e} | 1234.5 | 1.234500e+03 |
{0} {1} {0} | "a", "b" | a b a |
{{ }} | { } | |
{:.2Lf} + locale pt_BR | 1234.5 | 1.234,50 |
Exercícios
1. Duas casas
Complete para mostrar preco com duas casas decimais:
2. O cabeçalho
Qual cabeçalho traz std::println?
3. O que mostra std::println("[{:>5}]", 42);?
4. A Ana compila std::println("Oi") com g++ -Wall -Wextra oi.cpp (sem -std=) no g++ 15. O que acontece?
5. std::format("{} e {}", "Ana"), com um argumento só, dá:
Missão final: o recibo da papelaria
Missão: usando só std::println, faça o recibo sair exatamente assim: nome à esquerda numa coluna de 12, quantidade à direita numa coluna de 5, total à direita numa coluna de 9 com duas casas. O caderno custa R$ 12,50 e a caneta, R$ 2,50: calcule o total dentro do println.
Produto Qtd Total Caderno 3 37.50 Caneta 10 25.00
#include <print>
int main() {
std::println("Produto Qtd Total");
std::println("Caderno 3 37.5");
std::println("Caneta 10 25");
}
#include <print>
int main() {
std::println("{:<12}{:>5}{:>9}", "Produto", "Qtd", "Total");
std::println("{:<12}{:>5}{:>9.2f}", "Caderno", 3, 3 * 12.5);
std::println("{:<12}{:>5}{:>9.2f}", "Caneta", 10, 10 * 2.5);
}
Resumo
std::format("… {} …", valores)C++20 devolve umstd::string;std::printestd::printlnC++23 mostram na tela (oprintlnpula a linha).- Especificações depois de
:—{:.2f}casas,{:<10}/{:>10}/{:^10}alinhamento,{:05}zeros,{:x}hexadecimal. Nada gruda. {0},{1}escolhem o argumento;{{e}}escrevem chaves.- Ponto decimal sempre, a menos que você use
{:L}e um locale (std::locale("pt_BR.UTF-8")), que precisa estar instalado. - O compilador confere a string de formato: erro de formato é erro de compilação (
call to consteval function … is not a constant expression). - No g++ 15, compile com
-std=c++23: sem ele, nemformatnemprintexistem.