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.

Você vai aprender
  • Como o {} funciona e a diferença entre std::format, std::print e std::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 + iomanip e com println, lado a lado.
  • Como mostrar 1.234,50 no 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 {}:

JeitoCódigo
coutstd::cout << nome << " tem " << idade << " anos.\n";
formatstd::cout << std::format("{} tem {} anos.\n", nome, idade);
printlnstd::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 {} recebe nome, o segundo recebe idade.
std::string frase = …
O std::format nã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 com cout.

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::print e std::println. Repare que nem precisou de <iostream>.
std::print(...)
Formata e mostra. Não pula linha: você põe o \n se quiser.
std::println(...)
print line, "mostre a linha": formata, mostra e pula linha no final.
FunçãoCabeçalhoMostra na tela?Pula linha?Versão
std::format<format>Não, devolve um std::stringSó com \nC++20
std::print<print>SimSó com \nC++23
std::println<print>SimSim, sozinhoC++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:

ParteExemploO 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.
##xMostra o prefixo da base (0x, 0b).
0 + largura05Completa com zeros à esquerda (só para números).
largura10Largura mínima do campo.
. precisão.2Com f: casas decimais. Num texto: corta no tamanho.
tipof, e, x, X, b, o, df 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].

Nada gruda

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çãoUse
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 coutcout + <iomanip>
Coisas simples, sem formataçãoTanto 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:

  1. a letra L na especificação ({:L}, {:.2Lf}), que quer dizer "use as regras do locale";
  2. 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 L seguem 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 L nã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
O locale precisa existir no sistema

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 erradoNome que aparece no erroTradução
std::format("{} tem {} anos", "Ana")__invalid_arg_id_in_format_stringMais lacunas do que argumentos.
std::format("Total: {:.2f", 9.5)__unmatched_left_brace_in_format_stringUma { sem a } que a fecha.
std::format("Nota: {:.1f}", "dez")__failed_to_parse_format_specA 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).

A string de formato precisa estar escrita no código

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.

Apareceu {} 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

EscritaValorResultado
{}"Ana", 42, trueAna, 42, true
{:.2f}19.919.90
{:>10} / {:<10} / {:^10}"Ana"       Ana / Ana        /    Ana    
{:*^10}"Ana"***Ana****
{:05}4200042
{:x} / {:X} / {:#x}255ff / FF / 0xff
{:b}5101
{:+}5+5
{:e}1234.51.234500e+03
{0} {1} {0}"a", "b"a b a
{{ }}{ }
{:.2Lf} + locale pt_BR1234.51.234,50

Exercícios

1. Duas casas

Complete para mostrar preco com duas casas decimais:

std::println("R$ {}", preco);

2. O cabeçalho

Qual cabeçalho traz std::println?

#include <>

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 um std::string; std::print e std::println C++23 mostram na tela (o println pula 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, nem format nem print existem.