C++ Novidades do C++11 ao C++26
Desde 2011, o C++ ganha uma versão nova a cada três anos. Cada uma traz recursos que deixam o código mais curto e mais seguro: o auto, as lambdas, os ponteiros inteligentes, o std::optional, o std::print… Esta página é um mapa: para cada versão, uma tabela com o que mudou, um exemplo mínimo, a aula do guia que ensina o assunto e se o recurso já funciona no g++ 15 e no clang 21. Cada item foi compilado e executado no laboratório.
- Como escolher a versão do C++ na hora de compilar (
-std=c++23) e como o programa descobre a versão (__cplusplus). - O que cada versão trouxe de mais importante: C++11, C++14, C++17, C++20, C++23 e C++26.
- Como o mesmo programa fica em C++98 e em C++23.
- O que do C++26 já funciona no g++ 15 e o que ainda não chegou (reflexão, contratos,
std::inplace_vector). - Os erros que aparecem quando você usa um recurso novo com a versão errada.
A história completa das versões (quem decide, por que demorou de 1998 a 2011) está em Versões do C++. Aqui o foco é prático: o que você pode usar hoje. Todos os programas foram compilados na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/cpp/novidades, com o g++ 15.2 e o clang++ 21.1 do Ubuntu 26.04.
A ideia: atualizações de um celular
Pense no celular da Ana. De tempos em tempos chega uma atualização com recursos novos: um jeito mais fácil de tirar foto, um modo escuro. Os aplicativos antigos continuam funcionando; a atualização só acrescenta coisas.
O C++ é igual. Um programa escrito em 1998 ainda compila hoje, quase sempre sem mudar nada. Cada versão nova acrescenta ferramentas. Mas existe uma diferença importante: no celular, a atualização é instalada uma vez e pronto. No C++, você escolhe a versão toda vez que compila. Se você não escolher, o compilador usa uma versão padrão, que pode ser mais antiga do que você imagina.
| Versão | Ano | Em uma frase |
|---|---|---|
| C++98 / C++03 | 1998 / 2003 | O C++ "clássico". O C++03 só corrigiu detalhes. |
| C++11 | 2011 | A grande virada: auto, lambdas, nullptr, ponteiros inteligentes, movimento. Chamado de "C++ moderno". |
| C++14 | 2014 | Pequenos acertos no C++11: std::make_unique, lambdas genéricas. |
| C++17 | 2017 | Ferramentas do dia a dia: optional, variant, string_view, desestruturação, filesystem. |
| C++20 | 2020 | A segunda grande virada: concepts, ranges, std::format, módulos, <=>. |
| C++23 | 2023 | std::print, std::expected, import std. A versão que este guia ensina. |
| C++26 | 2026 | Reflexão e contratos. Os compiladores ainda estão implementando. |
1. Escolher a versão: -std=
Quem decide a versão é a opção -std= (de standard, "padrão") do compilador. Você já viu isso em Compilar com g++: o comando padrão do guia é g++ -std=c++23 -Wall -Wextra arquivo.cpp -o programa.
Dentro do programa, a versão aparece num número especial chamado __cplusplus (com dois sublinhados na frente). O compilador o preenche sozinho com o ano e o mês da versão. Este programa só mostra esse número:
#include <iostream>
int main() {
std::cout << __cplusplus << '\n';
}
A Ana compilou o mesmo arquivo com cada versão, de uma vez só, com um laço for do terminal (Bash). Não se preocupe com o laço: o que importa é a saída.
$ for s in 98 11 14 17 20 23 26; do g++ -std=c++$s versao.cpp -o versao && echo "c++$s: $(./versao)"; done c++98: 199711 c++11: 201103 c++14: 201402 c++17: 201703 c++20: 202002 c++23: 202302 c++26: 202400 « o C++26 ainda não tem número final no g++ 15 $ g++ versao.cpp -o versao && ./versao 201703 « sem -std: o g++ 15 usa o C++17
Repare na última linha. Sem -std=, o g++ 15 compila em C++17. Tudo o que veio no C++20 e no C++23 (std::format, std::print, ranges, contains) fica de fora. É por isso que o guia sempre escreve -std=c++23. No editor do guia, o Rodar já usa -std=c++23.
c++2c, c++2b, gnu++23: outros nomes
Antes de uma versão ter número oficial, os compiladores usam um apelido: c++2b era o C++23 e c++2c é o C++26. O g++ 15 aceita -std=c++26 e -std=c++2c (os dois são a mesma coisa). Já gnu++23 é "C++23 mais as extensões do GNU"; o guia usa sempre o c++23 puro, para o código funcionar em qualquer compilador.
2. C++11: a grande virada C++11
Depois de 13 anos sem novidades grandes, o C++11 mudou o jeito de escrever C++. Quase todo o "C++ moderno" que este guia ensina começa aqui.
| Recurso | Exemplo mínimo | Para que serve | Aula | g++ 15 |
|---|---|---|---|---|
auto | auto preco = 4.5; | O compilador descobre o tipo sozinho. | auto | sim |
| range-for | for (int n : notas) | Percorrer uma coleção sem contar posições. | range-for | sim |
| Lambdas | [](int x) { return x * 2; } | Uma função pequena, sem nome, escrita no meio do código. | Lambdas | sim |
nullptr | int* p = nullptr; | "Ponteiro que não aponta para nada", no lugar do antigo NULL. | nullptr | sim |
| Ponteiros inteligentes | std::unique_ptr, std::shared_ptr | Memória que se libera sozinha: adeus ao delete. | Ponteiros Inteligentes | sim |
| Movimento | b = std::move(a); | "Mudar" os dados de lugar em vez de copiar. | Semântica de Movimento | sim |
constexpr | constexpr int q = quadrado(5); | Calcular durante a compilação. | constexpr | sim |
enum class | enum class Cor { vermelho, verde }; | Enumerações que não se misturam com int. | Enums | sim |
| Inicialização com chaves | std::vector<int> notas{7, 9, 10}; | Criar uma coleção já com os valores. | std::vector | sim |
Este programa usa todos esses recursos de uma vez. Não se preocupe em entender cada linha agora: cada uma tem a sua aula. A ideia é ver que tudo compila e roda.
#include <iostream>
#include <memory>
#include <string>
#include <utility>
#include <vector>
enum class Cor { vermelho, verde };
constexpr int quadrado(int x) { return x * x; }
int main() {
auto preco = 4.5; // auto
std::vector<int> notas{7, 9, 10}; // lista entre chaves
for (int n : notas) std::cout << n << ' '; // range-for
std::cout << '\n';
auto dobro = [](int x) { return x * 2; }; // lambda
int* p = nullptr; // nullptr
std::unique_ptr<int> u(new int(42)); // smart pointer (C++11)
std::shared_ptr<int> s = std::make_shared<int>(7);
std::string a = "lista de compras";
std::string b = std::move(a); // move
constexpr int q = quadrado(5); // constexpr
Cor c = Cor::verde; // enum class
std::cout << preco << ' ' << dobro(21) << ' ' << (p == nullptr) << ' '
<< *u << ' ' << *s << ' ' << b << ' ' << q << ' '
<< (c == Cor::verde) << '\n';
}
$ g++ -std=c++23 -Wall -Wextra cpp11.cpp -o cpp11 $ ./cpp11 7 9 10 4.5 42 1 42 7 lista de compras 25 1
O 1 depois do 42 é o true de p == nullptr: o cout mostra os booleanos como 1 e 0 (veja Booleanos). O new int(42) da linha do unique_ptr é o jeito do C++11; o C++14 trouxe uma forma melhor, logo abaixo.
O mesmo programa, antes e depois
Para sentir a diferença, veja um programa da lista de compras da Ana escrito como se escrevia em 1998: um mapa de preços, uma lista e o total.
#include <iostream>
#include <map>
#include <string>
#include <vector>
int main() {
std::map<std::string, double> precos;
precos["pão"] = 7.5;
precos["leite"] = 5.25;
precos["café"] = 18.9;
std::vector<std::string> lista;
lista.push_back("café");
lista.push_back("pão");
double total = 0;
for (std::vector<std::string>::const_iterator it = lista.begin(); it != lista.end(); ++it) {
std::map<std::string, double>::const_iterator p = precos.find(*it);
if (p != precos.end()) {
std::cout << p->first << ": " << p->second << "\n";
total += p->second;
}
}
std::cout << "Total: " << total << "\n";
}
E agora o mesmo programa em C++23:
#include <map>
#include <print>
#include <string>
#include <vector>
int main() {
const std::map<std::string, double> precos{
{"pão", 7.5}, {"leite", 5.25}, {"café", 18.9}};
const std::vector<std::string> lista{"café", "pão"};
double total = 0;
for (const auto& item : lista) {
if (auto p = precos.find(item); p != precos.end()) {
const auto& [nome, preco] = *p;
std::println("{}: {}", nome, preco);
total += preco;
}
}
std::println("Total: {}", total);
}
$ g++ -std=c++98 -Wall -Wextra compras98.cpp -o compras98 && ./compras98 café: 18.9 pão: 7.5 Total: 26.4 $ g++ -std=c++23 -Wall -Wextra compras23.cpp -o compras23 && ./compras23 café: 18.9 pão: 7.5 Total: 26.4
A saída é idêntica. O que mudou foi o código:
| C++98 | C++ moderno | Versão |
|---|---|---|
Três linhas precos["pão"] = 7.5; | precos{{"pão", 7.5}, …}, e o mapa pode ser const | C++11 |
std::vector<std::string>::const_iterator it = … | for (const auto& item : lista) | C++11 |
find numa linha, if na outra | if (auto p = …; p != precos.end()): a variável só existe dentro do if | C++17 |
p->first e p->second | auto& [nome, preco] = *p;: nomes que dizem o que são | C++17 |
std::cout << … << "\n" | std::println("{}: {}", nome, preco) | C++23 |
Missão: este é o programa de 1998. Troque o laço com const_iterator por um range-for com auto: for (const auto& item : lista). Dentro dele, use item no lugar de *it. O total deve continuar 26.4.
#include <iostream>
#include <map>
#include <string>
#include <vector>
int main() {
std::map<std::string, double> precos;
precos["pão"] = 7.5;
precos["leite"] = 5.25;
precos["café"] = 18.9;
std::vector<std::string> lista;
lista.push_back("café");
lista.push_back("pão");
double total = 0;
for (std::vector<std::string>::const_iterator it = lista.begin(); it != lista.end(); ++it) {
std::map<std::string, double>::const_iterator p = precos.find(*it);
if (p != precos.end()) {
std::cout << p->first << ": " << p->second << "\n";
total += p->second;
}
}
std::cout << "Total: " << total << "\n";
}
#include <iostream>
#include <map>
#include <string>
#include <vector>
int main() {
std::map<std::string, double> precos;
precos["pão"] = 7.5;
precos["leite"] = 5.25;
precos["café"] = 18.9;
std::vector<std::string> lista;
lista.push_back("café");
lista.push_back("pão");
double total = 0;
for (const auto& item : lista) {
auto p = precos.find(item);
if (p != precos.end()) {
std::cout << p->first << ": " << p->second << "\n";
total += p->second;
}
}
std::cout << "Total: " << total << "\n";
}
3. C++14: acabamento C++14
O C++14 foi uma versão pequena, que arredondou o C++11. O item mais importante para você é o std::make_unique: com ele, o new some de vez do código do dia a dia.
| Recurso | Exemplo mínimo | Para que serve | Aula | g++ 15 |
|---|---|---|---|---|
std::make_unique | auto u = std::make_unique<int>(42); | Criar um unique_ptr sem escrever new. | Ponteiros Inteligentes | sim |
| Lambdas genéricas | [](auto x) { … } | Uma lambda que aceita qualquer tipo. | Lambdas | sim |
| Retorno deduzido | auto soma(int a, int b) { return a + b; } | A função descobre o próprio tipo de retorno. | Retorno | sim |
| Separador de dígitos | 1'000'000 | Números grandes fáceis de ler. | Inteiros | sim |
| Literal binário | 0b1010 | Escrever um número em base 2. | Inteiros | sim |
#include <iostream>
#include <memory>
auto soma(int a, int b) { return a + b; } // retorno deduzido
int main() {
auto u = std::make_unique<int>(42); // make_unique
auto mostra = [](auto x) { std::cout << x << '\n'; }; // lambda genérica
mostra(*u);
mostra("texto");
mostra(3.5);
int milhao = 1'000'000; // separador de dígitos
int bin = 0b1010; // literal binário
std::cout << milhao << ' ' << bin << ' ' << soma(2, 3) << '\n';
}
$ g++ -std=c++23 -Wall -Wextra cpp14.cpp -o cpp14 && ./cpp14 42 texto « a mesma lambda recebeu um int, um texto e um double 3.5 1000000 10 5 « 0b1010 é 10 em decimal
4. C++17: ferramentas do dia a dia C++17
O C++17 trouxe "caixinhas" que resolvem problemas comuns: um valor que pode não existir (optional), um valor que pode ser de dois tipos (variant), um texto que você só quer ler (string_view) e um jeito de mexer em pastas e arquivos (filesystem).
| Recurso | Exemplo mínimo | Para que serve | Aula | g++ 15 |
|---|---|---|---|---|
| Desestruturação (structured bindings) | auto [fruta, qtd] = par; | Dar nome às partes de um par, tupla ou struct. | pair, tuple e Desestruturação | sim |
std::optional | std::optional<int> idade; | "Talvez tenha um valor, talvez não." | optional e variant | sim |
std::variant | std::variant<int, std::string> v; | Um valor que pode ser de um tipo ou de outro. | optional e variant | sim |
std::string_view | void saudar(std::string_view nome) | Olhar um texto sem copiá-lo. | string_view | sim |
if com inicializador | if (auto it = m.find(k); it != m.end()) | Criar uma variável que só existe dentro do if. | if e else | sim |
std::filesystem | std::filesystem::current_path() | Pastas, arquivos, caminhos. | Arquivos | sim |
#include <filesystem>
#include <iostream>
#include <map>
#include <optional>
#include <string>
#include <string_view>
#include <variant>
std::optional<int> buscar(const std::map<std::string, int>& m, const std::string& k) {
if (auto it = m.find(k); it != m.end()) return it->second; // if com inicializador
return std::nullopt;
}
void saudar(std::string_view nome) { std::cout << "Oi, " << nome << '\n'; }
int main() {
std::map<std::string, int> estoque{{"maçã", 12}, {"pera", 0}};
for (const auto& [fruta, qtd] : estoque) // structured bindings
std::cout << fruta << ": " << qtd << '\n';
std::cout << buscar(estoque, "maçã").value_or(-1) << ' '
<< buscar(estoque, "uva").value_or(-1) << '\n';
std::variant<int, std::string> v = std::string("dez");
std::cout << std::get<std::string>(v) << '\n';
saudar("Ana");
std::cout << std::filesystem::current_path() << '\n';
}
$ g++ -std=c++23 -Wall -Wextra cpp17.cpp -o cpp17 && ./cpp17 maçã: 12 pera: 0 12 -1 « "uva" não existe: value_or devolveu o -1 de reserva dez Oi, Ana "/home/ana/cpp/novidades" « o caminho sai entre aspas quando vai direto para o cout
Missão: o laço usa par.first e par.second, que não dizem nada. Troque por uma desestruturação: for (const auto& [fruta, qtd] : estoque), e mostre maçã tem 12 e pera tem 0.
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, int> estoque{{"maçã", 12}, {"pera", 0}};
for (const auto& par : estoque) {
std::cout << par.first << ": " << par.second << '\n';
}
}
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, int> estoque{{"maçã", 12}, {"pera", 0}};
for (const auto& [fruta, qtd] : estoque) {
std::cout << fruta << " tem " << qtd << '\n';
}
}
5. C++20: a segunda grande virada C++20
O C++20 é tão grande quanto o C++11. Ele mudou como se escrevem templates (concepts), como se percorrem coleções (ranges), como se formata texto (std::format) e até como se organizam os arquivos (módulos).
| Recurso | Exemplo mínimo | Para que serve | Aula | g++ 15 |
|---|---|---|---|---|
| Concepts | template <std::integral T> | Dizer que tipo um template aceita, com erros legíveis. | Concepts | sim |
| Ranges | notas | std::views::filter(…) | Encadear filtros e transformações numa coleção. | Ranges | sim |
std::format | std::format("{} + {}", a, b) | Montar texto com marcadores {}. | format e print | sim |
<=> ("nave espacial") | auto operator<=>(const V&) const = default; | Ganhar <, >, ==… de uma vez. | Sobrecarga de Operadores | sim |
| Módulos | export module loja; / import loja; | Substituir os #include. | Módulos | sim, com -fmodules |
std::span | int soma(std::span<const int> v) | "Uma janela" para um pedaço de vector ou array. | std::vector | sim |
contains em map/set | m.contains("ana") | Perguntar se uma chave existe. | std::map | sim |
| Inicializadores designados | Ponto p{.x = 1, .y = 2}; | Preencher uma struct dizendo o nome de cada campo. | Structs | sim |
#include <algorithm>
#include <compare>
#include <concepts>
#include <format>
#include <iostream>
#include <map>
#include <ranges>
#include <span>
#include <vector>
template <std::integral T>
T dobro(T x) { return x * 2; }
struct Versao {
int maior, menor;
auto operator<=>(const Versao&) const = default;
};
int soma(std::span<const int> v) { int t = 0; for (int x : v) t += x; return t; }
int main() {
std::vector<int> notas{3, 8, 5, 10, 7};
auto aprovadas = notas | std::views::filter([](int n) { return n >= 6; });
for (int n : aprovadas) std::cout << n << ' ';
std::cout << '\n';
std::ranges::sort(notas);
std::cout << std::format("menor={} maior={} soma={}\n", notas.front(), notas.back(), soma(notas));
std::cout << dobro(21) << '\n';
std::cout << (Versao{1, 2} < Versao{1, 10}) << '\n';
std::map<std::string, int> m{{"ana", 1}};
std::cout << m.contains("ana") << '\n';
}
$ g++ -std=c++23 -Wall -Wextra cpp20.cpp -o cpp20 && ./cpp20 8 10 7 « só as notas maiores ou iguais a 6 menor=3 maior=10 soma=33 42 1 « a versão 1.2 vem antes da 1.10: o <=> comparou campo por campo 1
No g++ 15, módulos só funcionam com a opção -fmodules, além do -std=c++20 ou mais novo. O CMake e as IDEs ainda estão se acostumando com eles. A aula Módulos mostra o passo a passo; no dia a dia, os #include continuam sendo a forma mais comum.
6. C++23: a versão deste guia C++23
O C++23 completou o que o C++20 começou. O recurso que você mais vai ver no guia é o std::println: escrever na tela com marcadores {}, sem a fila de <<.
| Recurso | Exemplo mínimo | Para que serve | Aula | g++ 15 |
|---|---|---|---|---|
std::print / std::println | std::println("Olá, {}!", nome); | Mostrar texto formatado. O println já pula a linha. | format e print | sim |
std::expected | std::expected<int, std::string> | "Um resultado ou o motivo do erro", sem exceções. | optional e variant | sim |
std::ranges::to | … | std::ranges::to<std::vector>() | Transformar o resultado de um range num vector de verdade. | Ranges | sim |
import std; | import std; | A biblioteca padrão inteira numa linha, como módulo. | Módulos | sim, com -fmodules (veja abaixo) |
contains em string | frase.contains("compras") | Perguntar se um texto contém outro. | Funções de String | sim |
std::views::enumerate | for (auto [i, nome] : std::views::enumerate(nomes)) | Percorrer com a posição junto. | Ranges | sim |
std::flat_map | std::flat_map<std::string, int> | Um mapa guardado num vetor: mais rápido para ler. | std::map | sim |
#include <expected>
#include <print>
#include <ranges>
#include <string>
#include <vector>
std::expected<int, std::string> dividir(int a, int b) {
if (b == 0) return std::unexpected("divisão por zero");
return a / b;
}
int main() {
std::println("Olá, {}!", "Ana");
auto r = dividir(10, 0);
if (!r) std::println("erro: {}", r.error());
std::println("{}", dividir(10, 2).value());
auto pares = std::views::iota(1, 11)
| std::views::filter([](int x) { return x % 2 == 0; })
| std::ranges::to<std::vector>();
std::println("{}", pares);
std::string frase = "lista de compras";
std::println("{}", frase.contains("compras"));
}
$ g++ -std=c++23 -Wall -Wextra cpp23.cpp -o cpp23 && ./cpp23 Olá, Ana! erro: divisão por zero « dividir(10, 0) devolveu o erro, não um número 5 [2, 4, 6, 8, 10] « o println sabe mostrar um vector inteiro true « e mostra bool como true/false, não 1/0
Repare nas duas últimas linhas. O std::println mostra um vector inteiro com colchetes e vírgulas, e mostra booleanos como true/false. Com o cout, você teria de escrever um laço para o vetor e usar std::boolalpha para o booleano.
import std; no g++ 15
O import std; troca todos os #include da biblioteca padrão por uma linha. No g++ 15 ele funciona, mas a biblioteca precisa ser compilada como módulo na primeira vez. Isso se faz acrescentando o arquivo bits/std.cc (que vem com o g++) ao comando:
import std;
int main() {
std::println("Olá do import std!");
}
$ g++ -std=c++23 -fmodules -fsearch-include-path bits/std.cc importstd.cpp -o importstd $ ./importstd Olá do import std! $ ls gcm.cache std.gcm « a biblioteca padrão já compilada como módulo
- -fmodules
- Liga os módulos no g++.
- -fsearch-include-path bits/std.cc
- Procura o arquivo
bits/std.ccnas pastas de cabeçalhos do g++ e o compila junto. É ele que diz "o módulostdé feito destas partes". - gcm.cache/std.gcm
- O resultado: a biblioteca padrão "pré-digerida". Nas próximas compilações na mesma pasta, o g++ reaproveita esse arquivo.
É um recurso promissor, mas ainda pouco usado: o guia continua com os #include, que funcionam em todo lugar. No editor do guia (Clang), use também os #include.
Missão: a Ana quer só os produtos que custam menos de 10 reais. Complete a condição do filter e termine o encadeamento com | std::ranges::to<std::vector>(). O println deve mostrar [7.5, 5.25].
#include <print>
#include <ranges>
#include <vector>
int main() {
std::vector<double> precos{7.5, 18.9, 5.25, 32.0};
std::vector<double> baratos = precos
| std::views::filter([](double p) { return true; /* troque */ });
std::println("{}", baratos);
}
#include <print>
#include <ranges>
#include <vector>
int main() {
std::vector<double> precos{7.5, 18.9, 5.25, 32.0};
std::vector<double> baratos = precos
| std::views::filter([](double p) { return p < 10; })
| std::ranges::to<std::vector>();
std::println("{}", baratos);
}
Se você rodar a missão sem mexer, o compilador recusa. No g++, a linha principal do erro é esta:
$ g++ -std=c++23 -Wall -Wextra baratos.cpp -o baratos
baratos.cpp: In lambda function:
baratos.cpp:8:40: warning: unused parameter ‘p’ [-Wunused-parameter]
8 | | std::views::filter([](double p) { return true; /* troque */ });
| ~~~~~~~^
baratos.cpp: In function ‘int main()’:
baratos.cpp:8:9: error: conversion from ‘std::ranges::filter_view<std::ranges::ref_view<std::vector<double> >, main()::<lambda(double)> >’ to non-scalar type ‘std::vector<double>’ requested
7 | std::vector<double> baratos = precos
| ~~~~~~
8 | | std::views::filter([](double p) { return true; /* troque */ });
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
O aviso é só porque o p ainda não é usado. O erro, traduzido: "pediram uma conversão de filter_view para std::vector<double>". Um filter é só uma "vista" que escolhe elementos na hora em que você percorre; ele não é um vector. O ranges::to existe exatamente para fazer essa conversão. (O Clang do editor diz a mesma coisa com outras palavras: no viable conversion.)
7. C++26: o que já chegou e o que ainda não C++26
O C++26 é a versão mais nova. O comitê fechou o texto, mas os compiladores ainda estão implementando as partes. As duas novidades mais esperadas são a reflexão (o programa poder "olhar para si mesmo": listar os campos de uma struct, os nomes de um enum) e os contratos (escrever na própria função as condições que os argumentos devem cumprir). A Ana testou cada item com g++ -std=c++26 e clang++ -std=c++2c:
| Recurso | Exemplo mínimo | Para que serve | g++ 15 | clang 21 |
|---|---|---|---|---|
| Reflexão | std::meta::identifier_of(^^Cor) | Ler, durante a compilação, os nomes e campos dos seus tipos. | não | não |
| Contratos | int dividir(int a, int b) pre(b != 0) | Condições de entrada (pre) e saída (post) de uma função. | não | não |
std::inplace_vector | std::inplace_vector<int, 3> v; | Um vector com capacidade máxima fixa, sem alocar memória. | não | não |
Variável "não me interessa" _ | auto [_, idade] = pessoa; | Ignorar uma parte sem inventar um nome. | sim | sim |
= delete("motivo") | void pagar(int) = delete("use double"); | Proibir uma chamada explicando por quê. | sim | sim |
| Indexar pacotes | args...[0] | Pegar o primeiro argumento de um template variádico. | sim | sim |
#embed | #embed "lista.txt" | Colocar o conteúdo de um arquivo dentro do programa. | sim | sim, com aviso |
| Aritmética saturada | std::add_sat(a, b) | Somar sem "dar a volta": o resultado para no máximo do tipo. | sim | sim |
std::println() vazio | std::println(); | Só pular uma linha. | sim | sim |
As colunas valem para os compiladores do laboratório (Ubuntu 26.04). O editor do guia usa o Clang 22, um pouco mais novo, então pode aceitar algum item a mais com -std=c++26. As três primeiras linhas são as mais faladas, e ainda não estão no g++ 15. Veja o que ele responde a cada uma:
$ g++ -std=c++26 -Wall -Wextra c26_reflect.cpp -o c26_reflect
c26_reflect.cpp:1:10: fatal error: meta: Arquivo ou diretório inexistente
1 | #include <meta>
| ^~~~~~
compilation terminated.
$ g++ -std=c++26 -Wall -Wextra c26_inplace.cpp -o c26_inplace
c26_inplace.cpp:1:10: fatal error: inplace_vector: Arquivo ou diretório inexistente
1 | #include <inplace_vector>
| ^~~~~~~~~~~~~~~~
compilation terminated.
$ g++ -std=c++26 -Wall -Wextra c26_contracts.cpp -o c26_contracts
c26_contracts.cpp:3:27: error: expected initializer before ‘pre’
3 | int dividir(int a, int b) pre(b != 0) { return a / b; }
| ^~~
c26_contracts.cpp: In function ‘int main()’:
c26_contracts.cpp:6:24: error: ‘dividir’ was not declared in this scope
6 | std::println("{}", dividir(10, 2));
| ^~~~~~~
Os dois primeiros erros dizem a mesma coisa: o cabeçalho (<meta> da reflexão, <inplace_vector>) não existe na biblioteca do g++ 15. A parte "Arquivo ou diretório inexistente" sai em português porque vem do sistema, não do g++. No terceiro, o g++ não conhece a palavra pre naquele lugar e se perde: o segundo erro (‘dividir’ was not declared) é só consequência do primeiro. Enquanto os contratos não chegam, o jeito é conferir o argumento com um if ou um assert (veja Exceções).
Já os recursos menores funcionam. Este é o = delete("motivo"), que dá um erro com a explicação escrita por você:
void pagar(double valor);
void pagar(int) = delete("use double para dinheiro"); // C++26
int main() {
pagar(10);
}
$ g++ -std=c++26 -Wall -Wextra c26_delete.cpp -o c26_delete
c26_delete.cpp: In function ‘int main()’:
c26_delete.cpp:5:10: error: use of deleted function ‘void pagar(int)’: use double para dinheiro
5 | pagar(10);
| ~~~~~^~~~
c26_delete.cpp:2:6: note: declared here
2 | void pagar(int) = delete("use double para dinheiro"); // C++26
| ^~~~~
c26_delete.cpp:5:10: note: use ‘-fdiagnostics-all-candidates’ to display considered candidates
O 10 é um int, e a versão int foi apagada de propósito: o erro aparece com a frase "use double para dinheiro". E o _, que deixa você ignorar uma parte de uma desestruturação:
#include <print>
#include <utility>
int main() {
auto [_, idade] = std::pair{"Ana", 30}; // _ = "não me interessa" (C++26)
std::println("{}", idade);
}
$ g++ -std=c++26 -Wall -Wextra c26_placeholder.cpp -o c26_placeholder && ./c26_placeholder 30
Como o C++26 ainda está chegando aos compiladores, código com -std=c++26 pode não compilar no computador de outra pessoa. Para projetos, o C++23 (ou até o C++20) é a escolha segura em 2026. O editor do guia também usa -std=c++23.
Quando dá errado
-std=
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, int> estoque{{"maçã", 12}};
if (estoque.contains("maçã"))
std::cout << "tem maçã\n";
}
$ g++ -Wall -Wextra contem.cpp -o contem
contem.cpp: In function ‘int main()’:
contem.cpp:7:17: error: ‘class std::map<std::__cxx11::basic_string<char>, int>’ has no member named ‘contains’
7 | if (estoque.contains("maçã"))
| ^~~~~~~~
O que significa: "a classe std::map não tem nenhum membro chamado contains". Sem -std=, o g++ 15 compilou em C++17, e o contains só veio no C++20. O tipo comprido (std::__cxx11::basic_string<char>) é só o nome completo de std::string.
Como consertar: acrescente -std=c++23. Sempre que um recurso novo "não existe", desconfie primeiro da versão.
$ g++ -std=c++20 -Wall -Wextra imprime.cpp -o imprime
imprime.cpp: In function ‘int main()’:
imprime.cpp:4:10: error: ‘println’ is not a member of ‘std’
4 | std::println("Olá, {}!", "Ana");
| ^~~~~~~
imprime.cpp:4:10: note: ‘std::println’ is only available from C++23 onwards
$ g++ -std=c++23 -Wall -Wextra imprime.cpp -o imprime && ./imprime
Olá, Ana!
O que significa: "println não é membro de std". Aqui o g++ ajuda: a nota da última linha diz que std::println só existe "do C++23 em diante". O #include <print> não deu erro, mas, em C++20, o arquivo existe vazio.
Como consertar: -std=c++23, como na segunda tentativa.
$ g++ -std=c++98 -Wall -Wextra compras23.cpp -o x
compras23.cpp: In function ‘int main()’:
compras23.cpp:7:47: warning: extended initializer lists only available with ‘-std=c++11’ or ‘-std=gnu++11’ [-Wc++11-extensions]
7 | const std::map<std::string, double> precos{
| ^
compras23.cpp:7:41: error: in C++98 ‘precos’ must be initialized by constructor, not by ‘{...}’
O que significa: o aviso diz que "listas de inicialização estendidas" (as chaves {…}) só existem a partir do C++11, e o erro diz que, em C++98, o mapa teria de ser preenchido de outro jeito. Essa saída continua por dezenas de linhas, uma para cada recurso moderno do arquivo; só as primeiras aparecem aqui.
Como consertar: isso acontece com projetos antigos (um Makefile com -std=c++98) ou IDEs mal configuradas. Troque a opção para -std=c++23. Veja onde ela fica em Make e CMake.
Você viu esse erro na seção do C++26, com <meta> e <inplace_vector>. Ele quer dizer que o cabeçalho não existe na biblioteca do seu compilador: ou o recurso é novo demais, ou o nome está errado (<vetor>, <strings>). Confira o nome; se estiver certo, o seu compilador ainda não tem o recurso.
Referência
| Opção | __cplusplus | Recursos-chave |
|---|---|---|
-std=c++98 | 199711 | classes, templates, STL (vector, map, string) |
-std=c++11 | 201103 | auto, range-for, lambdas, nullptr, unique_ptr/shared_ptr, std::move, constexpr, enum class, {} |
-std=c++14 | 201402 | make_unique, lambdas genéricas, 1'000'000, 0b1010 |
-std=c++17 (padrão do g++ 15) | 201703 | desestruturação, optional, variant, string_view, if (init; cond), filesystem |
-std=c++20 | 202002 | concepts, ranges, format, <=>, módulos, span, contains |
-std=c++23 (o guia) | 202302 | print/println, expected, ranges::to, import std, string::contains |
-std=c++26 | 202400 no g++ 15 | reflexão e contratos (ainda não no g++ 15), _, delete("motivo"), #embed |
Exercícios
1. A Ana compilou com g++ programa.cpp -o programa, sem -std=. Em que versão o g++ 15 compilou?
2. Qual destes recursos não funciona no g++ 15?
3. Qual versão trouxe auto, lambdas, nullptr e os ponteiros inteligentes?
4. A mensagem ‘class std::map<…>’ has no member named ‘contains’ apareceu. Qual a causa mais provável?
5. Complete o comando para compilar em C++23 com os avisos ligados:
6. Qual valor de __cplusplus o g++ 15 mostra com -std=c++20?
Resumo
- Uma versão nova a cada três anos: C++11, 14, 17, 20, 23 e 26. O código antigo continua compilando.
- Você escolhe a versão com
-std=. Sem ela, o g++ 15 usa o C++17. O guia usa sempre-std=c++23. - C++11 e C++20 foram as grandes viradas; C++14 e C++23 completaram o que elas começaram; C++17 trouxe
optional,variante companhia. - Tudo do C++23 que o guia ensina funciona no g++ 15 e no clang 21.
import std;funciona no g++ 15 com-fmodulesebits/std.cc. - Do C++26, os recursos menores já funcionam; reflexão, contratos e
std::inplace_vectorainda não estão no g++ 15 nem no clang 21. - "Não é membro", "não existe" ou "fatal error: … inexistente" com um recurso novo: confira primeiro o
-std=.