Aprimoramentos de conformidade do C++, alterações de comportamento e correções de bugs no Visual Studio 2019
O Microsoft C/C++ no Visual Studio (MSVC) faz aprimoramentos de conformidade e correções de bugs em cada versão. Este artigo lista as melhorias por versão principal e depois pela versão. Para avançar diretamente para as alterações de uma versão específica, use a lista abaixo Neste artigo.
Este documento lista as alterações no Visual Studio 2019. Para ver um guia sobre as alterações no Visual Studio 2022, confira Aprimoramentos de conformidade do C++ no Visual Studio 2022. Para conhecer as alterações no Visual Studio 2017, confira Aprimoramentos de conformidade do C++ no Visual Studio 2017. Para ver uma lista completa dos aprimoramentos de conformidade anteriores, confira Visual C++, novidades de 2003 a 2015.
Aprimoramentos de conformidade no Visual Studio 2019 RTW (versão 16.0)
O Visual Studio 2019 RTW apresenta as melhorias de conformidade, correções de bug e alterações de comportamento a seguir no compilador do Microsoft C++.
Observação
Os recursos do C++20 só estavam disponíveis no modo /std:c++latest
no Visual Studio 2019 até que a implementação do C++20 fosse considerada concluída. O Visual Studio 2019 versão 16.11 apresenta o modo /std:c++20
do compilador. Neste artigo, os recursos que originalmente exigiam o modo /std:c++latest
agora funcionam no modo /std:c++20
ou posterior nas versões mais recentes do Visual Studio. Atualizamos a documentação para mencionar /std:c++20
, mesmo que essa opção não estivesse disponível quando os recursos foram lançados pela primeira vez.
Os módulos aprimorados dão suporte a modelos e à detecção de erros
Agora os módulos estão oficialmente no padrão C++20. O suporte aprimorado foi adicionado no Visual Studio 2017 versão 15.9. Para saber mais, confira Melhor detecção de erro e de suporte de modelo em Módulos do C++ com o MSVC 2017 versão 15.9.
Especificação modificada de tipo de agregação
A especificação de um tipo de agregação foi alterada no C++20 (confira Proibir agregações com construtores declarados pelo usuário). No Visual Studio 2019, em /std:c++latest
(ou /std:c++20
no Visual Studio 2019 versão 16.11 e posteriores), uma classe com qualquer construtor declarado pelo usuário (por exemplo, incluindo um construtor declarado = default
ou = delete
) não é uma agregação. Anteriormente, somente os construtores fornecidos pelo usuário desqualificariam uma classe de uma agregação. Essa alteração impõe mais restrições sobre como esses tipos podem ser inicializados.
O código a seguir é compilado sem erros no Visual Studio 2017, mas gera os erros C2280 e C2440 no Visual Studio 2019 em /std:c++20
ou /std:c++latest
:
struct A
{
A() = delete; // user-declared ctor
};
struct B
{
B() = default; // user-declared ctor
int i = 0;
};
A a{}; // ill-formed in C++20, previously well-formed
B b = { 1 }; // ill-formed in C++20, previously well-formed
Suporte parcial para operator <=>
P0515R3 O C++20 apresenta o operador de comparação de três vias <=>
, também conhecido como o "operador espaçonave". O Visual Studio 2019 versão 16.0 no modo /std:c++latest
introduz suporte parcial para o operador gerando erros de sintaxe que agora não é permitida. Por exemplo, o seguinte código é compilado sem erros no Visual Studio 2017, mas gera vários erros no Visual Studio 2019 em /std:c++20
ou /std:c++latest
:
struct S
{
bool operator<=(const S&) const { return true; }
};
template <bool (S::*)(const S&) const>
struct U { };
int main(int argc, char** argv)
{
U<&S::operator<=> u; // In Visual Studio 2019 raises C2039, 2065, 2146.
}
Para evitar os erros, insira um espaço na linha incorreta antes do colchete angular final: U<&S::operator<= > u;
.
Referências a tipos sem qualificadores CV incompatíveis
Observação
Essa alteração afeta apenas as versões do Visual Studio 2019 16.0 a 16.8. Ela foi revertida desde o Visual Studio 2019 versão 16.9
Anteriormente, o MSVC permitia associação direta de uma referência a partir de um tipo com qualificadores CV incompatíveis abaixo do nível superior. Essa associação poderia permitir a modificação de dados const supostamente mencionados pela referência.
O compilador do Visual Studio 2019 versões 16.0 a 16.8 cria um temporário, como era exigido pelo padrão na época. Mais tarde, o padrão mudou retroativamente tornando o comportamento antigo do Visual Studio 2017 e anteriores correto e o comportamento do Visual Studio 2019 versão 16.0 a 16.8 incorreto. Consequentemente, essa alteração foi revertida desde o Visual Studio 2019 versão 16.9.
Confira Tipos semelhantes e associação de referência para ver uma alteração relacionada.
Por exemplo, no Visual Studio 2017, o código a seguir é compilado sem avisos. No Visual Studio 2019 versões 16.0 a 16.8, o compilador gera um aviso C4172. Desde o Visual Studio 2019 versão 16.9, o código é compilado novamente sem avisos:
struct X
{
const void* const& PData() const
{
return _pv;
}
void* _pv;
};
int main()
{
X x;
auto p = x.PData(); // C4172 <func:#1 "?PData@X@@QBEABQBXXZ"> returning address of local variable or temporary
}
reinterpret_cast
de uma função sobrecarregada
O argumento para reinterpret_cast
não é um dos contextos em que o endereço de uma função sobrecarregada é permitido. O código a seguir é compilado sem erros no Visual Studio 2017, mas, no Visual Studio 2019 gera o erro C2440:
int f(int) { return 1; }
int f(float) { return .1f; }
using fp = int(*)(int);
int main()
{
fp r = reinterpret_cast<fp>(&f); // C2440: cannot convert from 'overloaded-function' to 'fp'
}
Para evitar o erro, use uma conversão permitida para esse cenário:
int f(int);
int f(float);
using fp = int(*)(int);
int main()
{
fp r = static_cast<fp>(&f); // or just &f;
}
Fechamentos lambda
No C++ 14, os tipos de fechamento lambda não são literais. A consequência principal dessa regra é que o lambda não pode ser atribuído a uma variável constexpr
. O código a seguir é compilado sem erros no Visual Studio 2017, mas, no Visual Studio 2019 gera o erro C2127:
int main()
{
constexpr auto l = [] {}; // C2127 'l': illegal initialization of 'constexpr' entity with a non-constant expression
}
Para evitar o erro, remova o qualificador constexpr
ou altere o modo de conformidade para /std:c++17
ou posterior.
Códigos de falha de std::create_directory
P1164 implementado de C++20 incondicionalmente. Isso altera std::create_directory
para verificar se o destino já foi um diretório em caso de falha. Anteriormente, todos os erros do tipo ERROR_ALREADY_EXISTS foram convertidos em códigos de êxito, mas não criados pelo diretório.
operator<<(std::ostream, nullptr_t)
Por LWG 2221, adicionado operator<<(std::ostream, nullptr_t)
para gravação de nullptr
em fluxos.
Mais algoritmos paralelos
Novas versões paralelas de is_sorted
, is_sorted_until
, is_partitioned
, set_difference
, set_intersection
, is_heap
e is_heap_until
.
Correções na inicialização atômica
P0883 "Corrigindo inicialização atômica" altera std::atomic
para o valor-inicializar o contido em T
, em vez do padrão-inicializar. A correção é habilitada ao usar o Clang/LLVM com a biblioteca padrão da Microsoft. No momento, ela está desabilitada para o compilador do Microsoft C++ como uma solução alternativa para um bug no processamento de constexpr
.
remove_cvref
e remove_cvref_t
Implementação das características de tipo remove_cvref
e remove_cvref_t
de P0550. Elas removem a capacidade de referência e a qualificação CV de um tipo sem funções e matrizes decrescentes para ponteiros (diferentemente de std::decay
e std::decay_t
).
Macros de teste de recurso
P0941R2 – macros de teste de recurso está concluído, com suporte para __has_cpp_attribute
. Macros de teste de recurso têm suporte em todos os modos padrão.
Proibir agregações com construtores declarados pelo usuário
C++20 P1008R1 – proibição de agregações com construtores declarados pelo usuário está concluído.
reinterpret_cast
em uma função constexpr
Um reinterpret_cast
é ilegal em uma função constexpr
. O compilador do Microsoft C++ rejeitaria reinterpret_cast
previamente somente se ele fosse usado em um contexto constexpr
. No Visual Studio 2019, em todos os modos de padrões de linguagem, o compilador diagnostica corretamente um reinterpret_cast
na definição de uma função constexpr
. Agora, o código a seguir produz C3615:
long long i = 0;
constexpr void f() {
int* a = reinterpret_cast<int*>(i); // C3615: constexpr function 'f' cannot result in a constant expression
}
Para evitar o erro, remova o modificador constexpr
da declaração da função.
Diagnóstico correto para o construtor do intervalo basic_string
No Visual Studio 2019, o construtor de intervalo basic_string
não suprime mais o diagnóstico do compilador com static_cast
. O código a seguir é compilado sem avisos no Visual Studio 2017, apesar de possível perda de dados do wchar_t
ao char
ao inicializar out
:
std::wstring ws = /* . . . */;
std::string out(ws.begin(), ws.end()); // VS2019 C4244: 'argument': conversion from 'wchar_t' to 'const _Elem', possible loss of data.
O Visual Studio 2019 gera corretamente o aviso C4244. Para evitar o aviso, é possível inicializar o std::string
como mostrado neste exemplo:
std::wstring ws = L"Hello world";
std::string out;
for (wchar_t ch : ws)
{
out.push_back(static_cast<char>(ch));
}
Chamadas incorretas a +=
e -=
em /clr
ou /ZW
não são detectadas corretamente
Um bug foi introduzido no Visual Studio 2017 e fazia com que o compilador ignorasse silenciosamente erros e não gerasse código para as chamadas inválidas a +=
e -=
em /clr
ou /ZW
. O seguinte código é compilado sem erros no Visual Studio 2017, mas, no Visual Studio 2019, gera corretamente o erro C2845:
public enum class E { e };
void f(System::String ^s)
{
s += E::e; // in VS2019 C2845: 'System::String ^': pointer arithmetic not allowed on this type.
}
Para evitar o erro deste exemplo, use o operador +=
com o método ToString()
: s += E::e.ToString();
.
Inicializadores de membros de dados estáticos embutidos
Agora os acessos do membro inválido dentro de inline
e de static constexpr
são detectados corretamente. O exemplo a seguir é compilado sem erro no Visual Studio 2017, mas, no Visual Studio 2019, no modo /std:c++17
ou posterior, gera o erro C2248:
struct X
{
private:
static inline const int c = 1000;
};
struct Y : X
{
static inline int d = c; // VS2019 C2248: cannot access private member declared in class 'X'.
};
Para evitar o erro, declare o membro X::c
como protegido:
struct X
{
protected:
static inline const int c = 1000;
};
C4800 restabelecido
O MSVC costumava ter um aviso de desempenho C4800 sobre a conversão implícita em bool
. Ele era muito inconveniente e não podia ser suprimido, o que nos levou a removê-lo do Visual Studio 2017. No entanto, durante o ciclo de vida do Visual Studio 2017, recebemos muitos comentários sobre os casos úteis que ele estava resolvendo. No Visual Studio 2019, estamos retornando um C4800 cuidadosamente ajustado com o C4165 explicativo. Ambos os avisos são fáceis de suprimir: usando uma conversão explícita ou por comparação com 0 do tipo apropriado. O C4800 é um aviso desativado por padrão de nível 4 e o C4165 é um aviso desativado por padrão de nível 3. Os dois podem ser descobertos usando a opção do compilador /Wall
.
O exemplo a seguir gera o C4800 e o C4165 em /Wall
:
bool test(IUnknown* p)
{
bool valid = p; // warning C4800: Implicit conversion from 'IUnknown*' to bool. Possible information loss
IDispatch* d = nullptr;
HRESULT hr = p->QueryInterface(__uuidof(IDispatch), reinterpret_cast<void**>(&d));
return hr; // warning C4165: 'HRESULT' is being converted to 'bool'; are you sure this is what you want?
}
Para evitar os avisos no exemplo anterior, é possível escrever o código da seguinte maneira:
bool test(IUnknown* p)
{
bool valid = p != nullptr; // OK
IDispatch* d = nullptr;
HRESULT hr = p->QueryInterface(__uuidof(IDispatch), reinterpret_cast<void**>(&d));
return SUCCEEDED(hr); // OK
}
A função de membro de classe local não tem um corpo
No Visual Studio 2017, o aviso C4822 é gerado somente quando a opção do compilador /w14822
é definida explicitamente. Ele não é mostrado com /Wall
. No Visual Studio 2019, o C4822 é um aviso desativado por padrão, o que o faz poder ser descoberto em /Wall
sem precisar definir /w14822
explicitamente.
void example()
{
struct A
{
int boo(); // warning C4822: Local class member function doesn't have a body
};
}
Corpos de modelo de função que contêm instruções if constexpr
No Visual Studio 2019 em /std:c++20
ou /std:c++latest
, os corpos da função de modelo que têm instruções if constexpr
têm verificações extra relacionadas à análise habilitadas. Por exemplo, no Visual Studio 2017, o seguinte código produz C7510 apenas se a opção /permissive-
estiver definida. No Visual Studio 2019, o mesmo código gera erros mesmo quando a opção /permissive
é definida:
// C7510.cpp
// compile using: cl /EHsc /W4 /permissive /std:c++latest C7510.cpp
#include <iostream>
template <typename T>
int f()
{
T::Type a; // error C7510: 'Type': use of dependent type name must be prefixed with 'typename'
// To fix the error, add the 'typename' keyword. Use this declaration instead:
// typename T::Type a;
if constexpr (a.val)
{
return 1;
}
else
{
return 2;
}
}
struct X
{
using Type = X;
constexpr static int val = 1;
};
int main()
{
std::cout << f<X>() << "\n";
}
Para evitar o erro, adicione a palavra-chave typename
à declaração de a
: typename T::Type a;
.
Não há suporte ao código do assembly embutido em uma expressão lambda
A equipe do Microsoft C++ recentemente tomou conhecimento de um problema de segurança em que o uso do assembler embutido em um lambda poderia levar à corrupção de ebp
(o registro do endereço de retorno) em runtime. Um invasor mal-intencionado possivelmente poderia tirar proveito desse cenário. O assembler embutido só tem suporte no x86, e a interação entre o assembler embutido e o restante do compilador é ruim. Considerando esses fatos e a natureza do problema, a solução mais segura era não permitir o assembler embutido dentro de uma expressão lambda.
O único uso do assembler embutido em uma expressão lambda que encontramos “em condições naturais” era capturar o endereço de retorno. Nesse cenário, é possível capturar o endereço de retorno em todas as plataformas usando simplesmente um _ReturnAddress()
intrínseco do compilador.
O código a seguir produz C7553 no Visual Studio 2017 15.9 e versões posteriores do Visual Studio:
#include <cstdio>
int f()
{
int y = 1724;
int x = 0xdeadbeef;
auto lambda = [&]
{
__asm { // C7553: inline assembler is not supported in a lambda
mov eax, x
mov y, eax
}
};
lambda();
return y;
}
Para evitar o erro, mova o código do assembly para uma função nomeada conforme mostrado no exemplo a seguir:
#include <cstdio>
void g(int& x, int& y)
{
__asm {
mov eax, x
mov y, eax
}
}
int f()
{
int y = 1724;
int x = 0xdeadbeef;
auto lambda = [&]
{
g(x, y);
};
lambda();
return y;
}
int main()
{
std::printf("%d\n", f());
}
Depuração do iterador e std::move_iterator
O recurso de depuração do iterador foi ensinado a desencapsular adequadamente o std::move_iterator
. Por exemplo, agora std::copy(std::move_iterator<std::vector<int>::iterator>, std::move_iterator<std::vector<int>::iterator>, int*)
pode envolver o caminho memcpy
rápido.
Correções para a imposição da palavra-chave <xkeycheck.h>
Foi corrigida a imposição da biblioteca padrão em <xkeycheck.h> para macros que substituem uma palavra-chave. A biblioteca agora emite a palavra-chave de problema real detectada em vez de uma mensagem genérica. Ela também dá suporte a palavras-chave do C++20 e evita enganar o IntelliSense dizendo que palavras-chave são macros.
Tipos de alocador não mais preteridos
std::allocator<void>
, std::allocator::size_type
e std::allocator::difference_type
não são mais preteridos.
Aviso correto para restringir conversões de cadeia de caracteres
Removido um static_cast
falso de std::string
que não foi chamado pelo padrão e que, acidentalmente, suprimiu avisos de restrição do C4244. Tentativas de chamar std::string::string(const wchar_t*, const wchar_t*)
agora emitem corretamente C4244 sobre restringir um wchar_t
em um char
.
Várias correções de exatidão do <filesystem>
- Correção de
std::filesystem::last_write_time
que falhava ao tentar alterar o último horário de gravação de um diretório. - O construtor
std::filesystem::directory_entry
agora armazena um resultado com falha, em vez de gerar uma exceção, quando um caminho de destino não existente é fornecido. - A versão de dois parâmetros
std::filesystem::create_directory
foi alterada para chamar a versão de um parâmetro, pois a funçãoCreateDirectoryExW
subjacente usariacopy_symlink
quandoexisting_p
fosse um symlink. std::filesystem::directory_iterator
não falha mais quando um symlink corrompido é encontrado.std::filesystem::space
agora aceita caminhos relativos.std::filesystem::path::lexically_relative
não é mais confundido com barras à direita, relatado como LWG 3096.- Solução de
CreateSymbolicLinkW
que rejeitava caminhos com barras à esquerda emstd::filesystem::create_symlink
. - Solução alternativa da função
delete
no modo de exclusão POSIX que existia no Windows 10 LTSB 1609, mas que, na verdade, não conseguia excluir arquivos. - Os operadores de atribuição de cópia e construtores de cópia do
std::boyer_moore_searcher
e dostd::boyer_moore_horspool_searcher
agora realmente copiam coisas.
Algoritmos paralelos no Windows 8 e posterior
A biblioteca de algoritmos paralelos agora usa devidamente a família WaitOnAddress
real no Windows 8 e posterior, em vez de sempre usar o Windows 7 e versões falsas anteriores.
Espaço em branco em std::system_category::message()
std::system_category::message()
agora corta o espaço em branco à direita da mensagem retornada.
Divisão por zero em std::linear_congruential_engine
Algumas condições que poderiam fazer std::linear_congruential_engine
disparar a divisão por 0 foram corrigidas.
Correções para desencapsulamento do iterador
Algumas máquinas de desembrulhamento do iterador foram expostas pela primeira vez para integração de programador e usuário no Visual Studio 2017 15.8. Elas foram descritas no artigo do Blog da Equipe do C++ Recursos e correções do STL no VS 2017 15.8. Esse computador não desembrulha mais iteradores derivados de iteradores de biblioteca padrão. Por exemplo, um usuário derivado de std::vector<int>::iterator
e que tenta personalizar o comportamento agora obtém seu comportamento personalizado ao chamar algoritmos de biblioteca padrão, em vez do comportamento de um ponteiro.
Agora, a função reserve
de contêiner não ordenada na verdade reserva N elementos, conforme descrito em LWG 2156.
Manipulação de tempo
Anteriormente, alguns valores temporais que eram passados para a biblioteca de simultaneidade estourariam, por exemplo,
condition_variable::wait_for(seconds::max())
. Agora corrigidos, os estouros mudaram o comportamento em um ciclo aparentemente aleatório de 29 dias (quando uint32_t milissegundos aceitos pelas APIs do Win32 subjacentes estouravam).Agora o cabeçalho <ctime> declara corretamente
timespec
etimespec_get
no namespacestd
, além de declará-los no namespace global.
Várias correções de contêineres
Muitas funções de contêiner interno da biblioteca padrão tornaram-se
private
para uma experiência aprimorada do IntelliSense. Outras correções para marcar membros comoprivate
são esperadas em versões posteriores do MSVC.Corrigimos problemas de correção de segurança de exceção que faziam com que contêineres baseados em nó, como
list
,map
eunordered_map
, se corrompessem. Durante uma operação de reatribuição depropagate_on_container_copy_assignment
ou depropagate_on_container_move_assignment
, liberaríamos o nó sentinela do contêiner com o alocador antigo, realizaríamos a atribuição de POCCA/POCMA pelo alocador antigo e tentaríamos adquirir o nó sentinela do novo alocador. Se essa alocação falhasse, o contêiner estaria corrompido. Ele não poderia nem mesmo ser destruído, pois ter um nó sentinela é uma invariável fixa da estrutura de dados. Esse código foi corrigido para criar o novo nó sentinela usando o alocador do contêiner de origem antes de destruir o nó sentinela existente.Os contêineres foram corrigidos para alocadores sempre copiar/mover/trocar de acordo com
propagate_on_container_copy_assignment
,propagate_on_container_move_assignment
epropagate_on_container_swap
, mesmo para alocadores declaradosis_always_equal
.Adição das sobrecargas para funções de membro extrair e mesclar do contêiner que aceitam contêineres rvalue. Para obter mais informações, consulte P0083 "Splicing Maps And Sets"
std::basic_istream::read
processamento de \r\n
=>\n
std::basic_istream::read
foi corrigido para não gravar em partes do buffer fornecido temporariamente como parte do processamento de \r\n
para processamento de \n
. Essa alteração perde um pouco da vantagem de desempenho que foi obtida no Visual Studio 2017 15.8 para leituras com mais de 4 K. No entanto, as melhorias na eficiência evitando três chamadas virtuais por caractere ainda estão presentes.
Construtor std::bitset
O construtor std::bitset
não lê mais os numerais um e zero em ordem inversa em conjuntos de bits grandes.
Regressão de std::pair::operator=
Correção de uma regressão no operador de atribuição std::pair
introduzida ao implementar LWG 2729 "SFINAE ausente em std::pair::operator=
";. Ele agora aceita corretamente tipos conversíveis em std::pair
novamente.
Contextos não deduzidos para add_const_t
Corrigimos um bug de características de tipo secundário, em que add_const_t
e funções relacionadas devem ser um contexto não deduzido. Em outras palavras, add_const_t
deve ser um alias para typename add_const<T>::type
, e não const T
.
Aprimoramentos de conformidade na versão 16.1
char8_t
P0482r6. O C++20 adiciona um novo tipo de caractere usado para representar unidades de código UTF-8. Os literais de cadeia de caracteres u8
no C++20 têm o tipo const char8_t[N]
, em vez de const char[N]
, que era o caso anteriormente. Alterações semelhantes foram propostas para o padrão C em N2231. Sugestões para correção de compatibilidade com versões anteriores do char8_t
são dadas em P1423r3. O compilador do Microsoft C++ adiciona suporte ao char8_t
no Visual Studio 2019 versão 16.1 quando você especifica a opção do compilador /Zc:char8_t
. Ele pode ser revertido para o comportamento do C++17 por meio de /Zc:char8_t-
. O compilador EDG que alimenta o IntelliSense ainda não dá suporte a ele no Visual Studio 2019 versão 16.1. Você poderá ver erros falsos somente do IntelliSense, que não afetam a compilação real.
Exemplo
const char* s = u8"Hello"; // C++17
const char8_t* s = u8"Hello"; // C++20
Metafunção de std::type_identity
e objeto de função de std::identity
P0887R1 type_identity. A extensão do modelo de classe std::identity
preterido foi removida e substituída pela metafunção std::type_identity
do C++20 e pelo objeto de função std::identity
. Ambos estão disponíveis somente em /std:c++latest
(/std:c++20
no Visual Studio 2019 versão 16.11 e posteriores).
O exemplo a seguir produz o aviso de substituição C4996 para std::identity
(definido em <type_traits>) no Visual Studio 2017:
#include <type_traits>
using T = std::identity<int>::type;
T x, y = std::identity<T>{}(x);
int i = 42;
long j = std::identity<long>{}(i);
O exemplo a seguir mostra como usar o novo std::identity
(definido no <functional>) juntamente com o novo std::type_identity
:
#include <type_traits>
#include <functional>
using T = std::type_identity<int>::type;
T x, y = std::identity{}(x);
int i = 42;
long j = static_cast<long>(i);
Verificações de sintaxe para lambdas genéricos
O novo processador lambda habilita algumas verificações sintacticas no modo de conformidade em lambdas genéricas, em /std:c++latest
(/std:c++20
no Visual Studio 2019 versão 16.11 e posteriores) ou em qualquer outro modo de linguagem com /Zc:lambda
no Visual Studio 2019 versão 16.9 ou posteriores (anteriormente disponível como /experimental:newLambdaProcessor
começando no Visual Studio 2019 versão 16.3).
O processador lambda herdado compila esse exemplo sem avisos, mas o novo processador lambda produz o erro C2760:
void f() {
auto a = [](auto arg) {
decltype(arg)::Type t; // C2760 syntax error: unexpected token 'identifier', expected ';'
};
}
O exemplo mostra a sintaxe correta, agora imposta pelo compilador:
void f() {
auto a = [](auto arg) {
typename decltype(arg)::Type t;
};
}
Pesquisa dependente de argumento para chamadas de função
P0846R0 (C++20) Maior capacidade de localizar os modelos de função por meio de pesquisa dependente de argumento para expressões de chamada de função com argumentos de modelo explícitos. /std:c++latest
Requer (ou /std:c++20
no Visual Studio 2019 versão 16.11 e posterior).
Inicialização designada
P0329R4 (C++20) A inicialização designada permite que membros específicos sejam selecionados na inicialização de agregação usando a sintaxe Type t { .member = expr }
. /std:c++latest
Requer (ou /std:c++20
no Visual Studio 2019 versão 16.11 e posterior).
Classificação da conversão de enumeração para seu tipo subjacente fixo
O compilador agora classifica as conversões de enumeração de acordo com N4800 11.3.3.2 Classificação de sequências de conversão implícita (4.2):
- Uma conversão que promove uma enumeração cujo tipo subjacente é fixado ao seu tipo subjacente é melhor do que uma que promove para o tipo subjacente promovido, se as duas forem diferentes.
Essa classificação de conversão não estava implementada corretamente antes do Visual Studio 2019 versão 16.1. O comportamento de conformidade pode alterar o comportamento de resolução de sobrecarga ou expor uma ambiguidade que não era detectada anteriormente.
Essa alteração de comportamento do compilador se aplica a todos os modos de /std
e é uma alteração de falha binária e de origem.
O exemplo a seguir demonstra como o comportamento do compilador muda no na versão 16.1 e posteriores:
#include <type_traits>
enum E : unsigned char { e };
int f(unsigned int)
{
return 1;
}
int f(unsigned char)
{
return 2;
}
struct A {};
struct B : public A {};
int f(unsigned int, const B&)
{
return 3;
}
int f(unsigned char, const A&)
{
return 4;
}
int main()
{
// Calls f(unsigned char) in 16.1 and later. Called f(unsigned int) in earlier versions.
// The conversion from 'E' to the fixed underlying type 'unsigned char' is better than the
// conversion from 'E' to the promoted type 'unsigned int'.
f(e);
// Error C2666. This call is ambiguous, but previously called f(unsigned int, const B&).
f(e, B{});
}
Funções da biblioteca padrão novas e atualizadas (C++20)
starts_with()
eends_with()
parabasic_string
ebasic_string_view
.contains()
para contêineres associativos.remove()
,remove_if()
, eunique()
paralist
eforward_list
agora retornamsize_type
.shift_left()
eshift_right()
adicionados ao <algoritmo>.
Aprimoramentos de conformidade na versão 16.2
noexcept
funcões constexpr
Funções constexpr
não são mais consideradas noexcept
por padrão quando usadas em uma expressão constante. Essa alteração de comportamento vem da resolução do CWG (Grupo de Trabalho Principal) CWG 1351 e está habilitada em /permissive-
. O exemplo a seguir é compilado no Visual Studio 2019 versão 16.1 e anteriores, mas produz C2338 no Visual Studio 2019 versão 16.2:
constexpr int f() { return 0; }
int main() {
static_assert(noexcept(f()), "f should be noexcept"); // C2338 in 16.2
}
Para corrigir o erro, adicione a expressão noexcept
à declaração de função:
constexpr int f() noexcept { return 0; }
int main() {
static_assert(noexcept(f()), "f should be noexcept");
}
Expressões binárias com diferentes tipos de enumeração
O C++20 preteriu as conversões aritméticas usuais em operandos, em que:
Um operando é de tipo de enumeração e
o outro é de um tipo de enumeração diferente ou de um tipo de ponto flutuante.
Para obter mais informações, consulte P1120R0.
No Visual Studio 2019 versão 16.2 e posterior, o código a seguir produz um aviso de nível 4, C5054, quando a opção /std:c++latest
do compilador está habilitada (/std:c++20
no Visual Studio 2019 versão 16.11 e posteriores):
enum E1 { a };
enum E2 { b };
int main() {
int i = a | b; // warning C5054: operator '|': deprecated between enumerations of different types
}
Para evitar o aviso, use static_cast
para converter o segundo operando:
enum E1 { a };
enum E2 { b };
int main() {
int i = a | static_cast<int>(b);
}
Usar uma operação binária entre uma enumeração e um tipo de ponto flutuante agora é um aviso de nível 1, C5055, quando a opção /std:c++latest
do compilador estiver habilitada (/std:c++20
no Visual Studio 2019 versão 16.11 e posteriores):
enum E1 { a };
int main() {
double i = a * 1.1;
}
Para evitar o aviso, use static_cast
para converter o segundo operando:
enum E1 { a };
int main() {
double i = static_cast<int>(a) * 1.1;
}
Igualdade e comparações relacionais de matrizes
A igualdade e as comparações relacionais entre dois operandos do tipo de matriz são preteridas no C++20 (P1120R0). Em outras palavras, uma operação de comparação entre duas matrizes (apesar das semelhanças de classificação e extensão) agora produz um aviso. No Visual Studio 2019 versão 16.2 e posteriores, o código a seguir produz um aviso de nível 1, C5056, quando a opção do compilador /std:c++latest
está habilitada (/std:c++20
no Visual Studio 2019 versão 16.11 e posteriores):
int main() {
int a[] = { 1, 2, 3 };
int b[] = { 1, 2, 3 };
if (a == b) { return 1; } // warning C5056: operator '==': deprecated for array types
}
Para evitar o aviso, você pode comparar os endereços dos primeiros elementos:
int main() {
int a[] = { 1, 2, 3 };
int b[] = { 1, 2, 3 };
if (&a[0] == &b[0]) { return 1; }
}
Para determinar se o conteúdo de duas matrizes é igual, use a função std::equal
:
std::equal(std::begin(a), std::end(a), std::begin(b), std::end(b));
Efeito da definição do operador de nave espacial em ==
e !=
Uma definição do operador de nave espacial (<=>
) sozinha não mais regenerará expressões envolvendo ==
ou !=
, a menos que o operador da nave espacial seja marcado como = default
(P1185R2). O exemplo a seguir é compilado no Visual Studio 2019 RTW e versão 16.1, mas produz C2678 no Visual Studio 2019 versão 16.2:
#include <compare>
struct S {
int a;
auto operator<=>(const S& rhs) const {
return a <=> rhs.a;
}
};
bool eq(const S& lhs, const S& rhs) {
return lhs == rhs; // error C2676
}
bool neq(const S& lhs, const S& rhs) {
return lhs != rhs; // error C2676
}
Para evitar o erro, defina operator==
ou declare-o como padrão:
#include <compare>
struct S {
int a;
auto operator<=>(const S& rhs) const {
return a <=> rhs.a;
}
bool operator==(const S&) const = default;
};
bool eq(const S& lhs, const S& rhs) {
return lhs == rhs;
}
bool neq(const S& lhs, const S& rhs) {
return lhs != rhs;
}
Melhorias da Biblioteca Padrão
- <charconv>
to_chars()
com precisão fixa/científica. (A precisão geral está atualmente planejada para 16.4.) - P0020R6:
atomic<float>
,atomic<double>
,atomic<long double>
- P0463R1: endian
- P0482R6: Suporte para a biblioteca de
char8_t
- P0600R1:
[[nodiscard]]
para o STL, Bloco 1 - P0653R2:
to_address()
- P0754R2: <versão>
- P0771R1:
noexcept
Para construtor de movimentação destd::function
Comparadores de const para contêineres associativos
Código para pesquisa e inserção em set
, map
, multiset
e multimap
foi mesclado para tamanho de código reduzido. As operações de inserção agora chamam a comparação “menor que” em um functor de comparação const
, da mesma forma que as operações de pesquisa já faziam anteriormente. O código a seguir é compilado no Visual Studio 2019 versão 16.1 e anteriores, mas gera C3848 no Visual Studio 2019 versão 16.2:
#include <iostream>
#include <map>
using namespace std;
struct K
{
int a;
string b = "label";
};
struct Comparer {
bool operator() (K a, K b) {
return a.a < b.a;
}
};
map<K, double, Comparer> m;
K const s1{1};
K const s2{2};
K const s3{3};
int main() {
m.emplace(s1, 1.08);
m.emplace(s2, 3.14);
m.emplace(s3, 5.21);
}
Para evitar o erro, crie o operador de comparação const
:
struct Comparer {
bool operator() (K a, K b) const {
return a.a < b.a;
}
};
Aprimoramentos de conformidade no Visual Studio 2019 versão 16.3
Operadores de extração de fluxo para char*
removidos
Operadores de extração de fluxo para caracteres de ponteiro para caracteres foram removidos e substituídos por operadores de extração para matriz de caracteres (por P0487R1). O WG21 considera que as sobrecargas removidas não são seguras. No modo /std:c++20
ou /std:c++latest
, o exemplo a seguir agora produz C2679:
// stream_extraction.cpp
// compile by using: cl /std:c++latest stream_extraction.cpp
#include <iostream>
#include <iomanip>
int main() {
char x[42];
char* p = x;
std::cin >> std::setw(42);
std::cin >> p; // C2679: binary '>>': no operator found which takes a right-hand operand of type 'char *' (or there is no acceptable conversion)
}
Para evitar o erro, use o operador de extração com uma variável char[]
:
#include <iostream>
#include <iomanip>
int main() {
char x[42];
std::cin >> std::setw(42);
std::cin >> x; // OK
}
Novas palavras-chave, requires
e concept
Novas palavras-chave, requires
e concept
, foram adicionadas ao compilador do Microsoft C++. Se você tentar usar alguma delas como identificador no modo /std:c++20
ou /std:c++latest
, o compilador gerará C2059 para indicar um erro de sintaxe.
Construtores como nomes de tipo não permitidos
O compilador não considera mais nomes de construtores como nomes de classe injetados neste caso: quando eles aparecem em um nome qualificado após um alias para uma especialização de modelo de classe. Anteriormente, os construtores podiam ser usados como um nome de tipo para declarar outras entidades. O exemplo a seguir agora produz o erro C3646:
#include <chrono>
class Foo {
std::chrono::milliseconds::duration TotalDuration{}; // C3646: 'TotalDuration': unknown override specifier
};
Para evitar o erro, declare TotalDuration
conforme mostrado aqui:
#include <chrono>
class Foo {
std::chrono::milliseconds TotalDuration {};
};
Verificação mais rigorosa das funções extern "C"
Se uma função extern "C"
foi declarada em namespaces diferentes, as versões anteriores do compilador do Microsoft C++ não verificavam se as declarações eram compatíveis. No Visual Studio 2019 versão 16.3 e posterior, o compilador verifica a compatibilidade. No modo /permissive-
, o código a seguir produz os erros C2371 e C2733:
using BOOL = int;
namespace N
{
extern "C" void f(int, int, int, bool);
}
void g()
{
N::f(0, 1, 2, false);
}
extern "C" void f(int, int, int, BOOL){}
// C2116: 'N::f': function parameter lists do not match between declarations
// C2733: 'f': you cannot overload a function with 'extern "C"' linkage
Para evitar os erros no exemplo anterior, use bool
em vez de BOOL
, de forma consistente nas duas declarações de f
.
Melhorias da Biblioteca Padrão
Os cabeçalhos não padrão <stdexcpt.h> e <typeinfo.h> foram removidos. O código que os inclui, portanto, deve incluir <exceção> e <typeinfo> de cabeçalhos padrão, respectivamente.
Aprimoramentos de conformidade no Visual Studio 2019 versão 16.4
Melhor imposição da pesquisa de nome de duas fases para ids qualificados em /permissive-
A pesquisa de nome em duas fases requer que os nomes não dependentes usados nos corpos do modelo fiquem visíveis para o modelo no tempo de definição. Anteriormente, esses nomes podiam ser encontrados quando o modelo era instanciado. Essa alteração facilita a gravação de código portátil e em conformidade no MSVC sob o sinalizador /permissive-
.
No Visual Studio 2019 versão 16.4 com o conjunto de sinalizadores /permissive-
, o exemplo a seguir produz um erro, pois N::f
não fica visível quando é definido o modelo f<T>
:
template <class T>
int f() {
return N::f() + T{}; // error C2039: 'f': is not a member of 'N'
}
namespace N {
int f() { return 42; }
}
Normalmente, esse erro pode ser corrigido incluindo cabeçalhos ausentes ou funções ou variáveis de declaração de encaminhamento, conforme mostrado no exemplo a seguir:
namespace N {
int f();
}
template <class T>
int f() {
return N::f() + T{};
}
namespace N {
int f() { return 42; }
}
Conversão implícita de expressões de constantes integrais em ponteiro nulo
O compilador MSVC agora implementa o CWG Problema 903 no modo de conformidade (/permissive-
). Essa regra não permite a conversão implícita de expressões constantes integrais (exceto o literal inteiro '0') em constantes de ponteiro nulo. O exemplo a seguir produz C2440 no modo de conformidade:
int* f(bool* p) {
p = false; // error C2440: '=': cannot convert from 'bool' to 'bool *'
p = 0; // OK
return false; // error C2440: 'return': cannot convert from 'bool' to 'int *'
}
Para corrigir o erro, use nullptr
em vez de false
. Um literal 0 ainda é permitido:
int* f(bool* p) {
p = nullptr; // OK
p = 0; // OK
return nullptr; // OK
}
Regras padrão para tipos de literais inteiros
No modo de conformidade (habilitado por /permissive-
), o MSVC usa as regras padrão para tipos de literais inteiros. Literais decimais muito grandes para caber em um signed int
receberam anteriormente o tipo unsigned int
. Agora, esses literais recebem o próximo signed
tipo inteiro maior, long long
. Além disso, literais com o sufixo 'll' que são muito grandes para caber em um tipo signed
recebem tipo unsigned long long
.
Essa alteração pode levar à geração de diferentes diagnósticos de aviso e diferenças de comportamento para operações aritméticas em literais.
O exemplo a seguir mostra o novo comportamento no Visual Studio 2019 versão 16.4. A variável i
agora é do tipo unsigned int
, portanto, o aviso é gerado. Os bits de alta ordem da variável j
são definidos como 0.
void f(int r) {
int i = 2964557531; // warning C4309: truncation of constant value
long long j = 0x8000000000000000ll >> r; // literal is now unsigned, shift will fill high-order bits with 0
}
O exemplo a seguir demonstra como manter o comportamento antigo e evitar os avisos e a alteração de comportamento em tempo de execução:
void f(int r) {
int i = 2964557531u; // OK
long long j = (long long)0x8000000000000000ll >> r; // shift will keep high-order bits
}
Parâmetros de função que são sombra de parâmetros de modelo
O compilador MSVC agora gera um erro quando um parâmetro de função que são sombra um parâmetro de modelo:
template<typename T>
void f(T* buffer, int size, int& size_read);
template<typename T, int Size>
void f(T(&buffer)[Size], int& Size) // error C7576: declaration of 'Size' shadows a template parameter
{
return f(buffer, Size, Size);
}
Para corrigir o erro, altere o nome de um dos parâmetros:
template<typename T>
void f(T* buffer, int size, int& size_read);
template<typename T, int Size>
void f(T (&buffer)[Size], int& size_read)
{
return f(buffer, Size, size_read);
}
Especializações fornecidas pelo usuário de características de tipo
Em conformidade com a subcláusula meta.rqmts do Standard, o compilador MSVC agora gera um erro quando encontra uma especialização definida pelo usuário de um dos modelos type_traits
especificados no namespace std
. A menos que especificado de outra forma, essas especializações resultarão em um comportamento indefinido. O exemplo a seguir tem um comportamento indefinido porque viola a regra e static_assert
falha com erro C2338.
#include <type_traits>
struct S;
template<>
struct std::is_fundamental<S> : std::true_type {};
static_assert(std::is_fundamental<S>::value, "fail");
Para evitar o erro, defina um struct herdado da preferência type_trait
e especialize que:
#include <type_traits>
struct S;
template<typename T>
struct my_is_fundamental : std::is_fundamental<T> {};
template<>
struct my_is_fundamental<S> : std::true_type { };
static_assert(my_is_fundamental<S>::value, "fail");
Alterações nos operadores de comparação fornecidos pelo compilador
O compilador MSVC agora implementa as seguintes alterações nos operadores de comparação por P1630R1 quando a opção /std:c++20
ou /std:c++latest
está habilitada:
O compilador não reescreverá mais as expressões usando operator==
se elas envolverem um tipo de retorno que não seja um bool
. Agora, o código a seguir produz o erro C2088:
struct U {
operator bool() const;
};
struct S {
U operator==(const S&) const;
};
bool neq(const S& lhs, const S& rhs) {
return lhs != rhs; // C2088: '!=': illegal for struct
}
Para evitar o erro, você deve definir explicitamente o operador necessário:
struct U {
operator bool() const;
};
struct S {
U operator==(const S&) const;
U operator!=(const S&) const;
};
bool neq(const S& lhs, const S& rhs) {
return lhs != rhs;
}
O compilador não definirá mais um operador de comparação padrão se ele for membro de uma classe de união. O exemplo a seguir agora produz o erro C2120:
#include <compare>
union S {
int a;
char b;
auto operator<=>(const S&) const = default;
};
bool lt(const S& lhs, const S& rhs) {
return lhs < rhs;
}
Para evitar o erro, defina um corpo para o operador:
#include <compare>
union S {
int a;
char b;
auto operator<=>(const S&) const { ... }
};
bool lt(const S& lhs, const S& rhs) {
return lhs < rhs;
}
O compilador não definirá mais um operador de comparação padrão se a classe contiver um membro de referência. Agora, o código a seguir produz o erro C2120:
#include <compare>
struct U {
int& a;
auto operator<=>(const U&) const = default;
};
bool lt(const U& lhs, const U& rhs) {
return lhs < rhs;
}
Para evitar o erro, defina um corpo para o operador:
#include <compare>
struct U {
int& a;
auto operator<=>(const U&) const { ... };
};
bool lt(const U& lhs, const U& rhs) {
return lhs < rhs;
}
Aprimoramentos de conformidade no Visual Studio 2019 versão 16.5
Declaração de especialização explícita sem um inicializador não é uma definição
Em /permissive-
, o MSVC agora impõe uma regra padrão de que declarações de especialização explícitas sem inicializadores não são definições. Anteriormente, a declaração seria considerada uma definição com um inicializador padrão. O efeito é observável em tempo de vínculo, uma vez que um programa dependendo desse comportamento pode ter símbolos não resolvidos. Este exemplo agora resulta em um erro:
template <typename> struct S {
static int a;
};
// In permissive-, this declaration isn't a definition, and the program won't link.
template <> int S<char>::a;
int main() {
return S<char>::a;
}
error LNK2019: unresolved external symbol "public: static int S<char>::a" (?a@?$S@D@@2HA) referenced in function _main at link time.
Para resolver o problema, adicione um inicializador:
template <typename> struct S {
static int a;
};
// Add an initializer for the declaration to be a definition.
template <> int S<char>::a{};
int main() {
return S<char>::a;
}
A saída de pré-processador preserva NewLines
O pré-processador experimental agora preserva NewLines e espaços em branco ao usar /P
ou /E
com /experimental:preprocessor
.
Dada esta fonte de exemplo,
#define m()
line m(
) line
O formato de saída anterior de /E
era:
line line
#line 2
A nova saída de /E
agora é:
line
line
As palavras-chave import
e module
são dependentes de contexto
Por P1857R1, import
e as diretivas de pré-processador module
têm novas restrições em sua sintaxe. Esse exemplo não compila mais:
import // Invalid
m; // error C2146: syntax error: missing ';' before identifier 'm'
Para resolver o problema, mantenha a importação na mesma linha:
import m; // OK
Remoção de std::weak_equality
e std::strong_equality
A mesclagem de P1959R0 requer que o compilador remova o comportamento e as referências aos tipos std::weak_equality
e std::strong_equality
.
O código neste exemplo não compila mais:
#include <compare>
struct S {
std::strong_equality operator<=>(const S&) const = default;
};
void f() {
nullptr<=>nullptr;
&f <=> &f;
&S::operator<=> <=> &S::operator<=>;
}
O exemplo agora leva a esses erros:
error C2039: 'strong_equality': is not a member of 'std'
error C2143: syntax error: missing ';' before '<=>'
error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
error C7546: binary operator '<=>': unsupported operand types 'nullptr' and 'nullptr'
error C7546: binary operator '<=>': unsupported operand types 'void (__cdecl *)(void)' and 'void (__cdecl *)(void)'
error C7546: binary operator '<=>': unsupported operand types 'int (__thiscall S::* )(const S &) const' and 'int (__thiscall S::* )(const S &) const'
Para resolver o problema, atualize para preferir os operadores relacionais internos e substitua os tipos removidos:
#include <compare>
struct S {
std::strong_ordering operator<=>(const S&) const = default; // prefer 'std::strong_ordering'
};
void f() {
nullptr != nullptr; // use pre-existing builtin operator != or ==.
&f != &f;
&S::operator<=> != &S::operator<=>;
}
Alterações do TLS Guard
Anteriormente, as variáveis thread-local em DLLs não eram inicializadas corretamente. Além do thread que carregava a DLL, elas não eram inicializadas antes do primeiro uso em threads que existiam antes da DLL ser carregada. Esse defeito já foi corrigido. As variáveis thread-local em uma DLL são inicializadas imediatamente antes de seu primeiro uso nesses threads.
Esse novo comportamento de teste para inicialização em usos de variáveis thread-local pode ser desabilitado usando a opção do compilador /Zc:tlsGuards-
. Ou, adicionando o atributo [[msvc:no_tls_guard]]
a variáveis locais de thread privadas.
Melhor diagnóstico de chamada para funções excluídas
Nosso compilador era mais permissivo sobre chamadas para funções excluídas anteriormente. Por exemplo, se as chamadas ocorressem no contexto de um corpo de modelo, não diagnosticaríamos a chamada. Além disso, se houvesse várias instâncias de chamadas para funções excluídas, emitiríamos apenas um diagnóstico. Agora, emitimos um diagnóstico para cada uma delas.
Uma consequência do novo comportamento pode produzir uma pequena alteração interruptiva: o código que chamasse uma função excluída não seria diagnosticado se nunca ela fosse necessária para a geração de código. Agora nós a diagnosticamos antecipadamente.
Este exemplo mostra o código que agora produz um erro:
struct S {
S() = delete;
S(int) { }
};
struct U {
U() = delete;
U(int i): s{ i } { }
S s{};
};
U u{ 0 };
error C2280: 'S::S(void)': attempting to reference a deleted function
note: see declaration of 'S::S'
note: 'S::S(void)': function was explicitly deleted
Para resolver o problema, remova chamadas para funções excluídas:
struct S {
S() = delete;
S(int) { }
};
struct U {
U() = delete;
U(int i): s{ i } { }
S s; // Do not call the deleted ctor of 'S'.
};
U u{ 0 };
Aprimoramentos de conformidade no Visual Studio 2019 versão 16.6
Fluxos de biblioteca padrão rejeitam inserções de tipos de caracteres codificados incorretamente
Tradicionalmente, inserir um wchar_t
em um std::ostream
, e inserir char16_t
ou char32_t
em um std::ostream
ou std::wostream
, gera seu valor integral. Inserir ponteiros nesses tipos de caractere gera o valor do ponteiro. Os programadores não acham intuitivo nenhum dos casos. Muitas vezes, eles esperam que a biblioteca padrão faça a transcodificação do caractere ou cadeia de caracteres com término nulo e gere o resultado.
A proposta C++20 P1423R3 adiciona sobrecargas de operador de inserção de fluxo excluídas para essas combinações de tipos de ponteiro de fluxo e caractere ou caractere. Em /std:c++20
ou /std:c++latest
, as sobrecargas tornam essas inserções mal formadas, em vez de se comportarem de maneira provavelmente não intencional. O compilador gera o erro C2280 quando um é encontrado. Você pode definir a macro "escape hatch" _HAS_STREAM_INSERTION_OPERATORS_DELETED_IN_CXX20
como 1
para restaurar o comportamento antigo. (A proposta também exclui operadores de inserção de fluxo para char8_t
. Nossa biblioteca padrão implementou sobrecargas semelhantes quando adicionamos suporte a char8_t
, portanto, o comportamento "errado" nunca esteve disponível para char8_t
).
Esta amostra demonstra o comportamento com essa alteração:
#include <iostream>
int main() {
const wchar_t cw = L'x', *pw = L"meow";
const char16_t c16 = u'x', *p16 = u"meow";
const char32_t c32 = U'x', *p32 = U"meow";
std::cout << cw << ' ' << pw << '\n';
std::cout << c16 << ' ' << p16 << '\n';
std::cout << c32 << ' ' << p32 << '\n';
std::wcout << c16 << ' ' << p16 << '\n';
std::wcout << c32 << ' ' << p32 << '\n';
}
O código agora produz estas mensagens de diagnóstico:
error C2280: 'std::basic_ostream<char,std::char_traits<char>> &std::<<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,wchar_t)': attempting to reference a deleted function
error C2280: 'std::basic_ostream<char,std::char_traits<char>> &std::<<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,char16_t)': attempting to reference a deleted function
error C2280: 'std::basic_ostream<char,std::char_traits<char>> &std::<<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,char32_t)': attempting to reference a deleted function
error C2280: 'std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &std::<<<std::char_traits<wchar_t>>(std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &,char16_t)': attempting to reference a deleted function
error C2280: 'std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &std::<<<std::char_traits<wchar_t>>(std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &,char32_t)': attempting to reference a deleted function
Você pode obter o efeito do comportamento antigo em todos os modos de linguagem convertendo tipos de caracteres em unsigned int
ou tipos de ponteiro para caractere em const void*
:
#include <iostream>
int main() {
const wchar_t cw = L'x', *pw = L"meow";
const char16_t c16 = u'x', *p16 = u"meow";
const char32_t c32 = U'x', *p32 = U"meow";
std::cout << (unsigned)cw << ' ' << (const void*)pw << '\n'; // Outputs "120 0052B1C0"
std::cout << (unsigned)c16 << ' ' << (const void*)p16 << '\n'; // Outputs "120 0052B1CC"
std::cout << (unsigned)c32 << ' ' << (const void*)p32 << '\n'; // Outputs "120 0052B1D8"
std::wcout << (unsigned)c16 << ' ' << (const void*)p16 << '\n'; // Outputs "120 0052B1CC"
std::wcout << (unsigned)c32 << ' ' << (const void*)p32 << '\n'; // Outputs "120 0052B1D8"
}
Tipo de retorno alterado de std::pow()
para std::complex
Anteriormente, a implementação do MSVC das regras de promoção para o tipo de retorno do modelo de função std::pow()
estava incorreta. Por exemplo, pow(complex<float>, int)
anteriormente retornava complex<float>
. Agora ele retorna complex<double>
corretamente. A correção foi implementada incondicionalmente para todos os modos padrão no Visual Studio 2019 versão 16.6.
Essa alteração pode causar erros do compilador. Por exemplo, anteriormente você poderia multiplicar pow(complex<float>, int)
por um float
. Como complex<T> operator*
espera argumentos do mesmo tipo, o exemplo a seguir agora emite o erro do compilador C2676:
// pow_error.cpp
// compile by using: cl /EHsc /nologo /W4 pow_error.cpp
#include <complex>
int main() {
std::complex<float> cf(2.0f, 0.0f);
(void) (std::pow(cf, -1) * 3.0f);
}
pow_error.cpp(7): error C2676: binary '*': 'std::complex<double>' does not define this operator or a conversion to a type acceptable to the predefined operator
Há muitas correções possíveis:
Altere o tipo do multiplicando
float
paradouble
. Esse argumento pode ser convertido diretamente em umcomplex<double>
para corresponder ao tipo retornado porpow
.Restrinja o resultado de
pow
paracomplex<float>
dizendocomplex<float>{pow(ARG, ARG)}
. Em seguida, você pode continuar multiplicando por um valorfloat
.Passe
float
em vez deint
parapow
. Essa operação pode ser mais lenta.Em alguns casos, você pode evitar
pow
totalmente. Por exemplo,pow(cf, -1)
pode ser substituído por divisão.
switch
avisos para C
No Visual Studio 2019 versão 16.6 e posteriores, o compilador implementa alguns avisos de C++ preexistente para código compilado como C. Os seguintes avisos agora estão habilitados em diferentes níveis: C4060, C4061, C4062, C4063, C4064, C4065, C4808 e C4809. Os avisos C4065 e C4060 estão desabilitados por padrão no C.
O avisos são acionados em instruções case
ausentes, enum
indefinidas e instruções switch bool
inválidas (ou seja, as que contêm muitos casos). Por exemplo:
#include <stdbool.h>
int main() {
bool b = true;
switch (b) {
case true: break;
case false: break;
default: break; // C4809: switch statement has redundant 'default' label;
// all possible 'case' labels are given
}
}
Para corrigir esse código, remova o caso redundante default
:
#include <stdbool.h>
int main() {
bool b = true;
switch (b) {
case true: break;
case false: break;
}
}
Classes sem nome em declarações typedef
No Visual Studio 2019 versão 16.6 e posteriores, o comportamento das declarações typedef
foi restrito à conformidade com P1766R1. Com essa atualização, classes sem nome em uma declaração typedef
não podem ter nenhum membro que não seja:
- membros de dados não estáticos sem inicializadores de membro padrão,
- classes de membro, ou
- enumerações de membro.
As mesmas restrições são aplicadas recursivamente a cada classe aninhada. A restrição destina-se a garantir a simplicidade de structs que têm nomes typedef
para fins de vinculação. Eles devem ser simples o suficiente para que nenhum cálculo de vinculação seja necessário antes que o compilador chegue ao nome typedef
da vinculação.
Essa alteração afeta todos os modos de padrão do compilador. Nos modos padrão (/std:c++14
) e /std:c++17
, o compilador emite o aviso C5208 para código não conforme. Se /permissive-
for especificado, o compilador emitirá o aviso C5208 como um erro em /std:c++14
e emitirá o erro C7626 em /std:c++17
. O compilador emite o erro C7626 para código não conforme quando /std:c++20
ou /std:c++latest
é especificado.
O exemplo a seguir mostra os constructos que não são mais permitidos em structs sem nome. Dependendo do modo de padrões especificado, são emitidos erros ou avisos C5208 ou C7626:
struct B { };
typedef struct : B { // inheriting from 'B'; ill-formed
void f(); // ill-formed
static int i; // ill-formed
struct U {
void f(); // nested class has non-data member; ill-formed
};
int j = 10; // default member initializer; ill-formed
} S;
O código acima pode ser corrigido dando um nome à classe sem nome:
struct B { };
typedef struct S_ : B {
void f();
static int i;
struct U {
void f();
};
int j = 10;
} S;
Importação de argumento padrão em C++/CLI
Um número crescente de APIs tem argumentos padrão no .NET Core. Portanto, agora damos suporte à importação de argumento padrão em C++/CLI. Essa alteração pode interromper o código existente em que várias sobrecargas são declaradas, como neste exemplo:
public class R {
public void Func(string s) {} // overload 1
public void Func(string s, string s2 = "") {} // overload 2;
}
Quando essa classe é importada para C++/CLI, uma chamada para uma das sobrecargas causa um erro:
(gcnew R)->Func("abc"); // error C2668: 'R::Func' ambiguous call to overloaded function
O compilador emite o erro C2668 porque ambas as sobrecargas correspondem a essa lista de argumentos. Na segunda sobrecarga, o segundo argumento é preenchido pelo argumento padrão. Para contornar esse problema, você pode excluir a sobrecarga redundante (1). Ou, usar a lista de argumentos completa e fornecer explicitamente os argumentos padrão.
Aprimoramentos de conformidade no Visual Studio 2019 versão 16.7
A definição é trivialmente copiada
C++20 alterou a definição de é trivialmente copiada. Quando uma classe tem um membro de dados não estático com tipo qualificado volatile
, ela não implica mais que qualquer construtor de cópia ou movimentação gerado pelo compilador, ou operador de atribuição de cópia ou movimentação, seja não trivial. O comitê padrão do C++ aplicou essa alteração retroativamente como um Relatório de Defeitos. No MSVC, o comportamento do compilador não muda em modos de linguagem diferentes, como /std:c++14
ou /std:c++latest
.
Veja um exemplo desse novo comportamento:
#include <type_traits>
struct S
{
volatile int m;
};
static_assert(std::is_trivially_copyable_v<S>, "Meow!");
Esse código não é compilado em versões do MSVC anteriores ao Visual Studio 2019 versão 16.7. Há um aviso de compilador desativado por padrão que você pode usar para detectar essa alteração. Se você compilar o código acima usando cl /W4 /w45220
, verá o seguinte aviso:
warning C5220: `'S::m': a non-static data member with a volatile qualified type no longer implies that compiler generated copy/move constructors and copy/move assignment operators are non trivial`
Conversões literais de ponteiro para membro e cadeia de caracteres em bool
são de estreitamento
O comitê padrão do C++ adotou recentemente o Relatório de Defeitos P1957R2, que considera T*
em bool
uma conversão de estreitamento. O MSVC corrigiu um bug em sua implementação, que anteriormente diagnosticaria T*
em bool
como estreitamento, mas não diagnosticaria a conversão de um literal de cadeia de caracteres em bool
ou de um ponteiro para membro em bool
.
O programa a seguir está mal formado no Visual Studio 2019 versão 16.7:
struct X { bool b; };
void f(X);
int main() {
f(X { "whoops?" }); // error: conversion from 'const char [8]' to 'bool' requires a narrowing conversion
int (X::* p) = nullptr;
f(X { p }); // error: conversion from 'int X::*' to 'bool' requires a narrowing conversion
}
Para corrigir esse código, adicione comparações explícitas em nullptr
ou evite contextos em que conversões de restrição sejam mal formadas:
struct X { bool b; };
void f(X);
int main() {
f(X { "whoops?" != nullptr }); // Absurd, but OK
int (X::* p) = nullptr;
f(X { p != nullptr }); // OK
}
nullptr_t
é apenas conversível para bool
como uma inicialização direta
No C++11, nullptr
só é conversível em bool
como uma conversão direta; por exemplo, quando você inicializa um bool
usando uma lista de inicializadores entre chaves. Essa restrição nunca foi imposta pelo MSVC. Agora, o MSVC implementa a regra em /permissive-
. As conversões implícitas agora são diagnosticadas como mal formadas. Uma conversão contextual para bool
ainda é permitida, porque a inicialização direta bool b(nullptr)
é válida.
Na maioria dos casos, o erro pode ser corrigido substituindo nullptr
por false
, conforme mostrado neste exemplo:
struct S { bool b; };
void g(bool);
bool h() { return nullptr; } // error, should be 'return false;'
int main() {
bool b1 = nullptr; // error: cannot convert from 'nullptr' to 'bool'
S s { nullptr }; // error: cannot convert from 'nullptr' to 'bool'
g(nullptr); // error: cannot convert argument 1 from 'nullptr' to 'bool'
bool b2 { nullptr }; // OK: Direct-initialization
if (!nullptr) {} // OK: Contextual conversion to bool
}
Comportamento de inicialização em conformidade para inicializações de matriz com inicializadores ausentes
Anteriormente, o MSVC tinha um comportamento de não conformidade para inicializações de matriz que tinham inicializadores ausentes. O MSVC sempre chamou o construtor padrão para cada elemento de matriz que não tinha um inicializador. O comportamento padrão é inicializar cada elemento com uma lista de inicializadores vazia entre chaves ({}
). O contexto de inicialização para uma lista de inicializadores vazia entre chaves é a inicialização de cópia, que não permite chamadas para construtores explícitos. Também pode haver diferenças de runtime, pois o uso de {}
para inicializar pode chamar um construtor que usa um std::initializer_list
, em vez do construtor padrão. O comportamento de conformidade está habilitado em /permissive-
.
Veja um exemplo desse comportamento alterado:
struct B {
explicit B() {}
};
void f() {
B b1[1]{}; // Error in /permissive-, because aggregate init calls explicit ctor
B b2[1]; // OK: calls default ctor for each array element
}
A inicialização de membros de classe com nomes sobrecarregados é sequenciada corretamente
Identificamos um bug na representação interna dos membros de dados de classe quando um nome de tipo também está sobrecarregado como o nome de um membro de dados. Esse bug causava inconsistências na inicialização agregada e na ordem de inicialização do membro. O código de inicialização gerado agora está correto. No entanto, essa alteração pode levar a erros ou avisos na origem que, inadvertidamente, dependiam dos membros mal ordenados, como neste exemplo:
// Compiling with /w15038 now gives:
// warning C5038: data member 'Outer::Inner' will be initialized after data member 'Outer::v'
struct Outer {
Outer(int i, int j) : Inner{ i }, v{ j } {}
struct Inner { int x; };
int v;
Inner Inner; // 'Inner' is both a type name and data member name in the same scope
};
Nas versões anteriores, o construtor inicializaria incorretamente o membro de dados Inner
antes do membro de dados v
. (O padrão C++ requer uma ordem de inicialização igual à ordem de declaração dos membros). Agora que o código gerado segue o padrão, a lista de inicializadores de membros está fora de ordem. O compilador gera um aviso para este exemplo. Para corrigi-lo, reordene a lista de inicializadores de membro para refletir a ordem de declaração.
Resolução de sobrecarga envolvendo sobrecargas e argumentos integrais long
O padrão C++ requer a classificação de uma conversão de long
em int
como uma conversão padrão. Os compiladores MSVC anteriores classificavam-no incorretamente como uma promoção integral, o que classificava mais alto para resolução de sobrecarga. Essa classificação pode fazer com que a resolução de sobrecarga seja resolvida com êxito quando deve ser considerada ambígua.
O compilador agora considera a classificação corretamente no modo /permissive-
. O código inválido é diagnosticado corretamente, como neste exemplo:
void f(long long);
void f(int);
int main() {
long x {};
f(x); // error: 'f': ambiguous call to overloaded function
f(static_cast<int>(x)); // OK
}
Você pode corrigir esse problema de várias maneiras:
No site de chamada, altere o tipo do argumento passado para
int
. Você pode alterar o tipo de variável ou convertê-lo.Se houver muitos sites de chamada, você poderá adicionar outra sobrecarga que usa um argumento
long
. Nessa função, converta e encaminhe o argumento para a sobrecargaint
.
Uso de variável indefinida com vinculação interna
As versões do MSVC antes do Visual Studio 2019 versão 16.7 aceitavam o uso de uma variável declarada extern
que tinha vinculação interna e não estivesse definida. Essas variáveis não podem ser definidas em nenhuma outra unidade de tradução e não podem formar um programa válido. O compilador agora diagnostica esse caso em tempo de compilação. O erro é semelhante ao erro para funções estáticas indefinidas.
namespace {
extern int x; // Not a definition, but has internal linkage because of the anonymous namespace
}
int main()
{
return x; // Use of 'x' that no other translation unit can possibly define.
}
Este programa compilava e vinculava incorretamente, mas agora emitirá o erro C7631.
error C7631: 'anonymous-namespace::x': variable with internal linkage declared but not defined
Essas variáveis devem ser definidas na mesma unidade de tradução em que são usadas. Por exemplo, você pode fornecer um inicializador explícito ou uma definição separada.
Completude de tipo e conversões de ponteiro derivado para base
Nos padrões C++ antes do C++20, uma conversão de uma classe derivada para uma classe base não exigia que a classe derivada fosse um tipo de classe completo. O comitê padrão do C++ aprovou uma alteração retroativa do Relatório de Defeitos que se aplica a todas as versões da linguagem C++. Essa alteração alinha o processo de conversão com características de tipo, como std::is_base_of
, que exigem que a classe derivada seja um tipo de classe completo.
Aqui está um exemplo:
template<typename A, typename B>
struct check_derived_from
{
static A a;
static constexpr B* p = &a;
};
struct W { };
struct X { };
struct Y { };
// With this change this code will fail as Z1 is not a complete class type
struct Z1 : X, check_derived_from<Z1, X>
{
};
// This code failed before and it will still fail after this change
struct Z2 : check_derived_from<Z2, Y>, Y
{
};
// With this change this code will fail as Z3 is not a complete class type
struct Z3 : W
{
check_derived_from<Z3, W> cdf;
};
Essa alteração de comportamento se aplica a todos os modos de linguagem C++ do MSVC, não apenas a /std:c++20
ou /std:c++latest
.
Conversões de estreitamento são diagnosticadas de forma mais consistente
O MSVC emite um aviso para conversões de estreitamento em um inicializador de lista entre chaves. Anteriormente, o compilador não diagnosticava conversões de estreitamento de tipos subjacentes maiores enum
para tipos integrais mais estreitos. (O compilador considerou incorretamente uma promoção integral em vez de uma conversão). Se a conversão de estreitamento for intencional, você poderá evitar o aviso usando um argumento de inicializador static_cast
. Ou escolher um tipo integral de destino maior.
Aqui está um exemplo de como usar um static_cast
explícito para resolver o aviso:
enum E : long long { e1 };
struct S { int i; };
void f(E e) {
S s = { e }; // warning: conversion from 'E' to 'int' requires a narrowing conversion
S s1 = { static_cast<int>(e) }; // Suppress warning with explicit conversion
}
Aprimoramentos de conformidade no Visual Studio 2019 versão 16.8
Extensão 'Class rvalue used as lvalue'
O MSVC tem uma extensão que permite usar um rvalue de classe como um lvalue. A extensão não estende o tempo de vida da classe rvalue e pode levar a um comportamento indefinido no runtime. Agora, aplicamos a regra padrão e não permitimos essa extensão em /permissive-
.
Se você ainda não puder usar /permissive-
, poderá usar /we4238
para não permitir explicitamente a extensão. Aqui está um exemplo:
// Compiling with /permissive- now gives:
// error C2102: '&' requires l-value
struct S {};
S f();
void g()
{
auto p1 = &(f()); // The temporary returned by 'f' is destructed after this statement. So 'p1' points to an invalid object.
const auto &r = f(); // This extends the lifetime of the temporary returned by 'f'
auto p2 = &r; // 'p2' points to a valid object
}
Extensão 'Especialização explícita no escopo não namespace'
O MSVC tinha uma extensão que permitia especialização explícita no escopo não namespace. Agora faz parte do padrão, após a resolução do CWG 727. No entanto, há diferenças de comportamento. Ajustamos o comportamento do nosso compilador para alinhar com o padrão.
// Compiling with 'cl a.cpp b.cpp /permissive-' now gives:
// error LNK2005: "public: void __thiscall S::f<int>(int)" (??$f@H@S@@QAEXH@Z) already defined in a.obj
// To fix the linker error,
// 1. Mark the explicit specialization with 'inline' explicitly. Or,
// 2. Move its definition to a source file.
// common.h
struct S {
template<typename T> void f(T);
template<> void f(int);
};
// This explicit specialization is implicitly inline in the default mode.
template<> void S::f(int) {}
// a.cpp
#include "common.h"
int main() {}
// b.cpp
#include "common.h"
Verificação se há tipos de classe abstrata
O C++20 Standard alterou o processo que os compiladores usam para detectar o uso de um tipo de classe abstrato como um parâmetro de função. Especificamente, não é mais um erro SFINAE. Anteriormente, se o compilador detectasse que uma especialização de um modelo de função teria uma instância de tipo de classe abstrata como um parâmetro de função, essa especialização seria considerada mal formada. Ela não seria adicionado ao conjunto de funções candidatas viáveis. No C++20, a verificação de um parâmetro de tipo de classe abstrata não acontece até que a função seja chamada. O efeito é que o código usado para compilar não causará um erro. Aqui está um exemplo:
class Node {
public:
int index() const;
};
class String : public Node {
public:
virtual int size() const = 0;
};
class Identifier : public Node {
public:
const String& string() const;
};
template<typename T>
int compare(T x, T y)
{
return x < y ? -1 : (x > y ? 1 : 0);
}
int compare(const Node& x, const Node& y)
{
return compare(x.index(), y.index());
}
int f(const Identifier& x, const String& y)
{
return compare(x.string(), y);
}
Anteriormente, a chamada para compare
teria tentado especializar o modelo de função compare
usando um argumento de modelo String
para T
. Ela falharia ao gerar uma especialização válida, pois String
é uma classe abstrata. O único candidato viável teria sido compare(const Node&, const Node&)
. No entanto, no C++20, a verificação do tipo de classe abstrata não acontece até que a função seja chamada. Portanto, a especialização compare(String, String)
é adicionada ao conjunto de candidatos viáveis e é escolhida como a melhor candidata porque a conversão de const String&
em String
é uma sequência de conversão melhor do que a conversão de const String&
em const Node&
.
No C++20, uma correção possível para este exemplo é usar conceitos; ou seja, alterar a definição de compare
para:
template<typename T>
int compare(T x, T y) requires !std::is_abstract_v<T>
{
return x < y ? -1 : (x > y ? 1 : 0);
}
Ou, se os conceitos do C++ não estiverem disponíveis, você poderá cair novamente no SFINAE:
template<typename T, std::enable_if_t<!std::is_abstract_v<T>, int> = 0>
int compare(T x, T y)
{
return x < y ? -1 : (x > y ? 1 : 0);
}
Suporte para P0960R3 – permitir a inicialização de agregações de uma lista de valores entre parênteses
O C++20 P0960R3 adiciona suporte para inicializar uma agregação usando uma lista de inicializadores entre parênteses. Por exemplo, o código a seguir é válido no C++20:
struct S {
int i;
int j;
};
S s(1, 2);
A maior parte desse recurso é aditiva, ou seja, o código agora compila e não era compilado antes. No entanto, isso altera o comportamento de std::is_constructible
. No modo C++17, static_assert
falha, mas no modo C++20 ele é bem-sucedido:
static_assert(std::is_constructible_v<S, int, int>, "Assertion failed!");
Se você usar esse tipo de característica para controlar a resolução de sobrecarga, poderá levar a uma alteração no comportamento entre o C++17 e o C++20.
Resolução de sobrecarga envolvendo modelos de função
Anteriormente, o compilador permitia que algum código compilasse em /permissive-
, que não deveria ser compilado. O efeito era que o compilador chamava a função errada, levando a uma alteração no comportamento do runtime:
int f(int);
namespace N
{
using ::f;
template<typename T>
T f(T);
}
template<typename T>
void g(T&& t)
{
}
void h()
{
using namespace N;
g(f);
}
A chamada para g
usa um conjunto de sobrecarga que contém duas funções, ::f
e N::f
. Como N::f
é um modelo de função, o compilador deve tratar o argumento de função como um contexto não deduzido. Isso significa que, nesse caso, a chamada de g
deve falhar, pois o compilador não pode deduzir um tipo para o parâmetro de modelo T
. Infelizmente, o compilador não descartava o fato de que ele já havia decidido que ::f
era uma boa correspondência para a chamada de função. Em vez de emitir um erro, o compilador gerava código para chamar g
usando ::f
como argumento.
Considerando que, em muitos casos, usar ::f
como argumento de função é o que o usuário espera, só emitiremos um erro se o código for compilado com /permissive-
.
Migrando de /await
para corrotinas C++20
As corrotinas padrão C++20 agora estão ativadas por padrão em /std:c++20
e /std:c++latest
. Elas diferem das Corrotinas TS e do suporte sob a opção /await
. A migração de /await
para corrotinas padrão pode exigir algumas alterações no fonte.
Palavras-chave não padrão
As palavras-chave antigas await
e yield
não têm suporte no modo C++20. Em vez disso, o código deve usar co_await
e co_yield
. O modo padrão também não permite o uso de return
em uma corrotina. Cada return
em uma corrotina deve usar co_return
.
// /await
task f_legacy() {
...
await g();
return n;
}
// /std:c++latest
task f() {
...
co_await g();
co_return n;
}
Tipos de initial_suspend/final_suspend
Em /await
, as funções iniciais de promessa e suspensão podem ser declaradas como retornando bool
. Esse comportamento não é padrão. No C++20, essas funções devem retornar um tipo de classe aguardado, geralmente um dos tipos triviais aguardados: std::suspend_always
se a função retornou anteriormente true
ou, std::suspend_never
se ela retornou false
.
// /await
struct promise_type_legacy {
bool initial_suspend() noexcept { return false; }
bool final_suspend() noexcept { return true; }
...
};
// /std:c++latest
struct promise_type {
auto initial_suspend() noexcept { return std::suspend_never{}; }
auto final_suspend() noexcept { return std::suspend_always{}; }
...
};
Tipo de yield_value
No C++20, a função de promessa yield_value
deve retornar um tipo aguardado. No modo /await
, a função yield_value
tinha permissão para retornar void
e era sempre suspender. Essas funções podem ser substituídas por uma função que retorna std::suspend_always
.
// /await
struct promise_type_legacy {
...
void yield_value(int x) { next = x; };
};
// /std:c++latest
struct promise_type {
...
auto yield_value(int x) { next = x; return std::suspend_always{}; }
};
Função de tratamento de exceção
/await
dá suporte a um tipo de promessa sem nenhuma função de tratamento de exceção ou uma função de tratamento de exceção nomeada set_exception
, que usa um std::exception_ptr
. No C++20, o tipo de promessa deve ter uma função nomeada unhandled_exception
que não usa argumentos. O objeto de exceção pode ser obtido de std::current_exception
, se necessário.
// /await
struct promise_type_legacy {
void set_exception(std::exception_ptr e) { saved_exception = e; }
...
};
// /std:c++latest
struct promise_type {
void unhandled_exception() { saved_exception = std::current_exception(); }
...
};
Tipos de retorno deduzidos de corrotinas sem suporte
O C++20 não dá suporte a corrotinas com um tipo de retorno que inclui um tipo de espaço reservado, como auto
. Tipos de retorno de corrotinas devem ser declarados explicitamente. Em /await
, esses tipos deduzidos sempre envolvem um tipo experimental e exigem a inclusão de um cabeçalho que defina o tipo necessário: um de std::experimental::task<T>
, std::experimental::generator<T>
ou std::experimental::async_stream<T>
.
// /await
auto my_generator() {
...
co_yield next;
};
// /std:c++latest
#include <experimental/generator>
std::experimental::generator<int> my_generator() {
...
co_yield next;
};
Tipo de retorno de return_value
O tipo de retorno da função de promessa return_value
deve ser void
. No modo /await
, o tipo de retorno pode ser qualquer coisa e é ignorado. Esse diagnóstico pode ajudar a detectar erros sutis, como quando o autor assume incorretamente que o valor retornado de return_value
será retornado a um chamador.
// /await
struct promise_type_legacy {
...
int return_value(int x) { return x; } // incorrect, the return value of this function is unused and the value is lost.
};
// /std:c++latest
struct promise_type {
...
void return_value(int x) { value = x; }; // save return value
};
Comportamento de conversão de objeto de retorno
Se o tipo de retorno declarado de uma corrotina não corresponder ao tipo de retorno da função de promessa get_return_object
, o objeto retornado de get_return_object
será convertido para o tipo de retorno da corrotina. Em /await
, essa conversão é feita antecipadamente, antes que o corpo da corrotina tenha a chance de ser executado. Em /std:c++20
ou /std:c++latest
, essa conversão é feita quando o valor é retornado ao chamador. Isso permite que as corrotinas que não suspendem no ponto de suspensão inicial façam uso do objeto retornado por get_return_object
no corpo da corrotina.
Parâmetros de promessa corrotina
No C++20, o compilador tenta passar os parâmetros de corrotina (se houver) para um construtor do tipo de promessa. Se falhar, ele tentará novamente com um construtor padrão. No modo /await
, apenas o construtor padrão era usado. Essa alteração poderá levar a uma diferença de comportamento se a promessa tiver vários construtores. Ou, se houver uma conversão de um parâmetro de corrotina para o tipo de promessa.
struct coro {
struct promise_type {
promise_type() { ... }
promise_type(int x) { ... }
...
};
};
coro f1(int x);
// Under /await the promise gets constructed using the default constructor.
// Under /std:c++latest the promise gets constructed using the 1-argument constructor.
f1(0);
struct Object {
template <typename T> operator T() { ... } // Converts to anything!
};
coro f2(Object o);
// Under /await the promise gets constructed using the default constructor
// Under /std:c++latest the promise gets copy- or move-constructed from the result of
// Object::operator coro::promise_type().
f2(Object{});
/permissive-
e os módulos C++20 estão ativados por padrão em /std:c++20
O suporte a módulos C++20 está ativado por padrão em /std:c++20
e /std:c++latest
. Para obter mais informações sobre essa alteração e os cenários em que module
e import
são tratados condicionalmente como palavras-chave, consulte o Suporte aos módulos padrão do C++20 com o MSVC no Visual Studio 2019 versão 16.8.
Como pré-requisito para o suporte a Módulos, permissive-
agora está habilitado quando /std:c++20
ou /std:c++latest
for especificado. Para obter mais informações, consulte /permissive-
.
Para o código que foi compilado anteriormente em /std:c++latest
e requer comportamentos de compilador não compatíveis, /permissive
pode ser especificado para desativar o modo de conformidade estrito no compilador. A opção do compilador deve aparecer depois de /std:c++latest
na lista de argumentos de linha de comando. No entanto, /permissive
resultará em um erro se o uso de Módulos for detectado:
erro C1214: Os módulos entram em conflito com o comportamento não padrão solicitado por 'option'
Os valores mais comuns para option são:
Opção | Descrição |
---|---|
/Zc:twoPhase- |
A pesquisa de nome em duas fases é necessária para módulos C++20 e está implícita por /permissive- . |
/Zc:hiddenFriend- |
As regras de pesquisa de nome amigo oculto padrão são necessárias para módulos C++20 e estão implícitas por /permissive- . |
/Zc:lambda- |
O processamento lambda padrão é necessário para módulos C++20 e está implícito pelo modo /std:c++20 ou posterior. |
/Zc:preprocessor- |
O pré-processador em conformidade é necessário apenas para uso e criação de unidade de cabeçalho no C++20. Módulos nomeados não exigem essa opção. |
A opção /experimental:module
ainda é necessária para usar os Módulos std.*
que são fornecidos com o Visual Studio, pois eles ainda não estão padronizados.
A opção /experimental:module
também implica /Zc:twoPhase
, /Zc:lambda
e /Zc:hiddenFriend
. Anteriormente, o código compilado com Módulos às vezes poderia ser compilado se o Módulo /Zc:twoPhase-
fosse apenas consumido. Esse comportamento não tem mais suporte.
Aprimoramentos de conformidade no Visual Studio 2019 versão 16.9
Inicialização de cópia temporária na inicialização direta de referência
O problema do Grupo de Trabalho Principal CWG 2267 lidou com uma inconsistência entre uma lista de inicializadores entre parênteses e uma lista de inicializadores entre chaves. A resolução harmoniza as duas formas.
O Visual Studio 2019 versão 16.9 implementa o comportamento alterado em todos os modos do compilador /std
. No entanto, como é potencialmente uma alteração de falha de origem, ela só terá suporte se o código for compilado usando /permissive-
.
Esta amostra demonstra a alteração no comportamento:
struct A { };
struct B {
explicit B(const A&);
};
void f()
{
A a;
const B& b1(a); // Always an error
const B& b2{ a }; // Allowed before resolution to CWG 2267 was adopted: now an error
}
Características do destruidor e subobjetos potencialmente construídos
O problema do Grupo de Trabalho Principal CWG 2336 aborda uma omissão sobre especificações implícitas de exceção de destruidores em classes que têm classes base virtuais. A omissão significava que um destruidor em uma classe derivada poderia ter uma especificação de exceção mais fraca que uma classe base, se a base fosse abstrata e tivesse uma base virtual
.
O Visual Studio 2019 versão 16.9 implementa o comportamento alterado em todos os modos do compilador /std
.
Esta amostra demonstra como a interpretação mudou:
class V {
public:
virtual ~V() noexcept(false);
};
class B : virtual V {
virtual void foo () = 0;
// BEFORE: implicitly defined virtual ~B() noexcept(true);
// AFTER: implicitly defined virtual ~B() noexcept(false);
};
class D : B {
virtual void foo ();
// implicitly defined virtual ~D () noexcept(false);
};
Antes dessa alteração, o destruidor implicitamente definido B
era noexcept
, porque apenas subobjetos potencialmente construídos são considerados. E a classe base V
não é um subobjeto potencialmente construído, porque é uma base virtual
e B
é abstrata. No entanto, a classe base V
é um subobjeto potencialmente construído da classe D
, e por isso D::~D
é determinado como sendo noexcept(false)
, levando a uma classe derivada com uma especificação de exceção mais fraca que sua base. Essa interpretação não é segura. Ela pode levar a um comportamento de runtime incorreto se uma exceção for gerada de um destruidor de uma classe derivada de B.
Com essa alteração, um destruidor também será potencialmente gerado se ele tiver um destruidor virtual e qualquer classe base virtual tiver um destruidor potencialmente gerado.
Tipos semelhantes e associação de referência
O problema do Grupo de Trabalho Principal CWG 2352 lida com uma inconsistência entre as regras de associação de referência e as alterações para semelhança de tipo. A inconsistência foi introduzida em relatórios de defeito anteriores (como o CWG 330). Isso afetou as versões 16.0 a 16.8 do Visual Studio 2019.
Com essa alteração, a partir do Visual Studio 2019 versão 16.9, o código que antes vinculava uma referência a um temporário no Visual Studio 2019 versão 16.0 a 16.8 já pode ser associado diretamente quando os tipos envolvidos diferem apenas por qualificadores CV.
O Visual Studio 2019 versão 16.9 implementa o comportamento alterado em todos os modos do compilador /std
. É potencialmente uma alteração interruptiva de origem.
Confira Referências a tipos com qualificadores cv incompatíveis para ver uma alteração relacionada.
Esta amostra demonstra o comportamento alterado:
int *ptr;
const int *const &f() {
return ptr; // Now returns a reference to 'ptr' directly.
// Previously returned a reference to a temporary and emitted C4172
}
A atualização pode alterar o comportamento do programa que se baseava em um temporário introduzido:
int func() {
int i1 = 13;
int i2 = 23;
int* iptr = &i1;
int const * const& iptrcref = iptr;
// iptrcref is a reference to a pointer to i1 with value 13.
if (*iptrcref != 13)
{
return 1;
}
// Now change what iptr points to.
// Prior to CWG 2352 iptrcref should be bound to a temporary and still points to the value 13.
// After CWG 2352 it is bound directly to iptr and now points to the value 23.
iptr = &i2;
if (*iptrcref != 23)
{
return 1;
}
return 0;
}
Alteração de comportamento das opções /Zc:twoPhase
e /Zc:twoPhase-
Normalmente, as opções do compilador MSVC funcionam com o princípio de que o último visto ganha. Infelizmente, não foi o caso com as opções /Zc:twoPhase
e /Zc:twoPhase-
. Essas opções eram "autoadesivas", então as opções posteriores não podiam substituí-las. Por exemplo:
cl /Zc:twoPhase /permissive a.cpp
Nesse caso, a primeira opção, /Zc:twoPhase
, habilita a pesquisa estrita de nome de duas fases. A segunda opção destina-se a desabilitar o modo de conformidade estrito (é o oposto de /permissive-
), mas não desabilitava /Zc:twoPhase
.
O Visual Studio 2019 versão 16.9 altera esse comportamento em todos os modos do compilador /std
. /Zc:twoPhase
e /Zc:twoPhase-
não são mais "autoadesivas" e as opções posteriores podem substituí-las.
Especificadores de noexcept explícitos em modelos de destruidor
O compilador aceitou anteriormente um modelo de destruidor declarado com uma especificação de exceção sem lançamento, mas definido sem um especificador de noexcept explícito. A especificação de exceção implícita de um destruidor depende das propriedades da classe - propriedades que podem não ser conhecidas no ponto de definição de um modelo. O C++ Standard também requer esse comportamento: se um destruidor for declarado sem um especificador de noexcept, ele terá uma especificação de exceção implícita e nenhuma outra declaração da função poderá ter um especificador de noexcept.
O Visual Studio 2019 versão 16.9 foi alterado para ter o comportamento de conformidade em todos os modos do compilador /std
.
Esta amostra demonstra a alteração no comportamento do compilador:
template <typename T>
class B {
virtual ~B() noexcept; // or throw()
};
template <typename T>
B<T>::~B() { /* ... */ } // Before: no diagnostic.
// Now diagnoses a definition mismatch. To fix, define the implementation by
// using the same noexcept-specifier. For example,
// B<T>::~B() noexcept { /* ... */ }
Expressões reescritas em C++20
Desde o Visual Studio 2019 versão 16.2, em /std:c++latest
, o compilador aceitava código como este exemplo:
#include <compare>
struct S {
auto operator<=>(const S&) const = default;
operator bool() const;
};
bool f(S a, S b) {
return a < b;
}
No entanto, o compilador não invocava a função de comparação que o autor poderia esperar. O código acima deve ter a < b
reescrito como (a <=> b) < 0
. Em vez disso, o compilador usou a função de conversão definida pelo usuário, operator bool()
, e comparou com bool(a) < bool(b)
. No Visual Studio 2019 versão 16.9 e posteriores, o compilador reescreve a expressão usando a expressão de operador de espaçonave esperada.
Alteração da falha de origem
Aplicar corretamente conversões a expressões reescritas tem outro efeito: o compilador também diagnostica corretamente ambiguidades de tentativas de reescrever a expressão. Considere este exemplo:
struct Base {
bool operator==(const Base&) const;
};
struct Derived : Base {
Derived();
Derived(const Base&);
bool operator==(const Derived& rhs) const;
};
bool b = Base{} == Derived{};
No C++17, esse código seria aceito por causa da conversão derivada para base de Derived
do lado direito da expressão. No C++20, o candidato à expressão sintetizada também foi adicionado: Derived{} == Base{}
. Devido às regras no padrão sobre qual função ganha com base em conversões, acontece que a escolha entre Base::operator==
e Derived::operator==
é impossível. Como as sequências de conversão nas duas expressões não são melhores ou piores umas das outras, o código de exemplo resulta em uma ambiguidade.
Para resolver a ambiguidade, adicione um novo candidato que não estará sujeito às duas sequências de conversão:
bool operator==(const Derived&, const Base&);
Alteração de interrupção de runtime
Devido às regras de reescrita do operador no C++20, é possível que a resolução de sobrecarga encontre um novo candidato que ele não encontraria de outra forma em um modo de linguagem inferior. E, o novo candidato pode ser uma correspondência melhor que o candidato mais velho. Considere este exemplo:
struct iterator;
struct const_iterator {
const_iterator(const iterator&);
bool operator==(const const_iterator &ci) const;
};
struct iterator {
bool operator==(const const_iterator &ci) const { return ci == *this; }
};
No C++17, o único candidato para ci == *this
é const_iterator::operator==
. É uma correspondência porque *this
passa por uma conversão derivada para base em const_iterator
. No C++20, outro candidato reescrito é adicionado: *this == ci
, que invoca iterator::operator==
. Esse candidato não requer conversões, portanto, é uma correspondência melhor que const_iterator::operator==
. O problema com o novo candidato é que ela é a função que é definida no momento, portanto, a nova semântica da função causa uma definição infinitamente recursiva de iterator::operator==
.
Para ajudar no código como o exemplo, o compilador implementa um novo aviso:
$ cl /std:c++latest /c t.cpp
t.cpp
t.cpp(8): warning C5232: in C++20 this comparison calls 'bool iterator::operator ==(const const_iterator &) const' recursively
Para corrigir o código, seja explícito sobre qual conversão a ser usada:
struct iterator {
bool operator==(const const_iterator &ci) const { return ci == static_cast<const const_iterator&>(*this); }
};
Aprimoramentos de conformidade no Visual Studio 2019 versão 16.10
Sobrecarga errada escolhida para inicialização de cópia de classe
Dado este código de exemplo:
struct A { template <typename T> A(T&&); };
struct B { operator A(); };
struct C : public B{};
void f(A);
f(C{});
Versões anteriores do compilador convertiam incorretamente o argumento de f
, do tipo C
em um A
usando o construtor de conversão de modelo de A
. O Standard C++ requer o uso do operador de conversão B::operator A
. No Visual Studio 2019 versão 16.10 e posteriores, o comportamento de resolução de sobrecarga foi alterado para usar a sobrecarga correta.
Essa alteração também pode corrigir a sobrecarga escolhida em algumas outras situações:
struct Base
{
operator char *();
};
struct Derived : public Base
{
operator bool() const;
};
void f(Derived &d)
{
// Implicit conversion to bool previously used Derived::operator bool(), now uses Base::operator char*.
// The Base function is preferred because operator bool() is declared 'const' and requires a qualification
// adjustment for the implicit object parameter, while the Base function does not.
if (d)
{
// ...
}
}
Análise incorreta de literais de ponto flutuante
No Visual Studio 2019 versão 16.10 e posteriores, literais de ponto flutuante são analisados com base em seu tipo real. Versões anteriores do compilador sempre analisavam um literal de ponto flutuante como se tivesse o tipo double
, e, em seguida, convertiam o resultado para o tipo real. Esse comportamento pode levar a arredondamento incorreto e rejeição de valores válidos:
// The binary representation is '0x15AE43FE' in VS2019 16.9
// The binary representation is '0x15AE43FD' in VS2019 16.10
// You can use 'static_cast<float>(7.038531E-26)' if you want the old behavior.
float f = 7.038531E-26f;
Ponto de declaração incorreto
Versões anteriores do compilador não podiam compilar código autorreferencial como este exemplo:
struct S {
S(int, const S*);
int value() const;
};
S s(4, &s);
O compilador não declararia a variável s
até analisar toda a declaração, incluindo os argumentos do construtor. A pesquisa por s
na lista de argumentos do construtor falharia. No Visual Studio 2019 versão 16.10 e posteriores, este exemplo agora é compilado corretamente.
Infelizmente, essa alteração pode interromper o código existente, como neste exemplo:
S s(1, nullptr); // outer s
// ...
{
S s(s.value(), nullptr); // inner s
}
Em versões anteriores do compilador, quando ele pesquisava por s
nos argumentos do construtor para a declaração "interna" de s
, ele localizava a declaração anterior ("externa" s
) e o código era compilado. A partir da versão 16.10, o compilador emite o aviso C4700. Isso ocorre porque o compilador agora declara a s
"interna" antes de analisar os argumentos do construtor. Portanto, a pesquisa por s
localiza a "interna" de s
, que ainda não foi inicializada.
Membro explicitamente especializado de um modelo de classe
Versões anteriores do compilador marcavam incorretamente uma especialização explícita de um membro de modelo de classe como inline
se ele também tivesse sido definido no modelo principal. Esse comportamento significava que o compilador às vezes rejeitava um código em conformidade. No Visual Studio 2019 versão 16.10 e posteriores, uma especialização explícita não é mais marcada implicitamente como inline
no modo /permissive-
. Considere este exemplo:
Arquivo de origem s.h
:
// s.h
template<typename T>
struct S {
int f() { return 1; }
};
template<> int S<int>::f() { return 2; }
Arquivo de origem s.cpp
:
// s.cpp
#include "s.h"
Arquivo de origem main.cpp
:
// main.cpp
#include "s.h"
int main()
{
}
Para resolver o erro do vinculador no exemplo acima, adicione inline
explicitamente a S<int>::f
:
template<> inline int S<int>::f() { return 2; }
Mangling de nome de tipo de retorno deduzido
No Visual Studio 2019 versão 16.10 e posteriores, o compilador alterou a forma como gera nomes danificados para funções que deduzem tipos de retorno. Por exemplo, considere as funções:
auto f() { return 0; }
auto g() { []{}; return 0; }
Versões anteriores do compilador geravam esses nomes para o vinculador:
f: ?f@@YAHXZ -> int __cdecl f(void)
g: ?g@@YA@XZ -> __cdecl g(void)
Surpreendentemente, o tipo de retorno seria omitido de g
devido a outro comportamento semântico causado pelo lambda local no corpo da função. Essa inconsistência dificultava a implementação de funções exportadas que têm um tipo de retorno deduzido: a interface do módulo requer informações sobre como o corpo de uma função foi compilado. Ela precisa das informações para produzir uma função no lado da importação que possa se vincular corretamente à definição.
O compilador agora omite o tipo de retorno de uma função de tipo de retorno deduzida. Esse comportamento é consistente com outras implementações principais. Há uma exceção para modelos de função: esta versão do compilador apresenta um novo comportamento de nome mangled para modelos de função que têm um tipo de retorno deduzido:
template <typename T>
auto f(T) { return 1; }
template <typename T>
decltype(auto) g(T) { return 1.; }
int (*fp1)(int) = &f;
double (*fp2)(int) = &g;
Os nomes mangled para auto
e decltype(auto)
agora aparecem no binário, não no tipo de retorno deduzido:
f: ??$f@H@@YA?A_PH@Z -> auto __cdecl f<int>(int)
g: ??$g@H@@YA?A_TH@Z -> decltype(auto) __cdecl g<int>(int)
Versões anteriores do compilador incluíam o tipo de retorno deduzido como parte da assinatura. Quando o compilador incluía o tipo de retorno no nome mangled, ele poderia causar problemas no vinculador. Alguns cenários bem formados se tornariam ambíguos para o vinculador.
O novo comportamento do compilador pode produzir uma alteração interruptiva binária. Considere este exemplo:
Arquivo de origem a.cpp
:
// a.cpp
auto f() { return 1; }
Arquivo de origem main.cpp
:
// main.cpp
int f();
int main() { f(); }
Em versões anteriores à versão 16.10, o compilador produzia um nome para auto f()
que se parecia com int f()
, mesmo que fossem funções semanticamente distintas. Isso significa que o exemplo seria compilado. Para corrigir o problema, não confie em auto
na definição original de f
. Em vez disso, escreva-a como int f()
. Como as funções que deduzem tipos de retorno são sempre compiladas, as implicações para a ABI são minimizadas.
Aviso para atributo nodiscard
ignorado
As versões anteriores do compilador ignoravam silenciosamente determinados usos de um atributo nodiscard
. Eles ignoravam o atributo se ele estivesse em uma posição sintática que não se aplicasse à função ou classe que estava sendo declarada. Por exemplo:
static [[nodiscard]] int f() { return 1; }
No Visual Studio 2019 versão 16.10 e posteriores, em vez disso, o compilador emite um aviso de nível 4, C5240:
a.cpp(1): warning C5240: 'nodiscard': attribute is ignored in this syntactic position
Para corrigir esse problema, mova o atributo para a posição sintática correta:
[[nodiscard]] static int f() { return 1; }
Aviso para diretivas include
com nomes de cabeçalho do sistema no Purview do módulo
No Visual Studio 2019 versão 16.10 e posteriores, o compilador emite um aviso para evitar um erro de criação de interface de módulo comum. Se você incluir um cabeçalho de biblioteca padrão após uma instrução export module
, o compilador emitirá o aviso C5244. Aqui está um exemplo:
export module m;
#include <vector>
export
void f(std::vector<int>);
O desenvolvedor provavelmente não pretendia que o módulo m
possuísse o conteúdo de <vector>
. O compilador agora emite um aviso para ajudar a localizar e corrigir o problema:
m.ixx(2): warning C5244: '#include <vector>' in the purview of module 'm' appears erroneous. Consider moving that directive before the module declaration, or replace the textual inclusion with an "import <vector>;".
m.ixx(1): note: see module 'm' declaration
Para corrigir esse problema, mova #include <vector>
antes de export module m;
:
#include <vector>
export module m;
export
void f(std::vector<int>);
Aviso para funções de vinculação interna não usadas
No Visual Studio 2019 versão 16.10 e posteriores, o compilador avisa em mais situações em que uma função não relacionada com o vínculo interno foi removida. Versões anteriores do compilador emitiam um aviso C4505 para o código a seguir:
static void f() // warning C4505: 'f': unreferenced function with internal linkage has been removed
{
}
O compilador agora também alerta sobre funções auto
não referenciadas e funções não referenciadas em namespaces anônimos. Ele emite um aviso C5245 desativada por padrão para as duas funções a se:
namespace
{
void f1() // warning C5245: '`anonymous-namespace'::f1': unreferenced function with internal linkage has been removed
{
}
}
auto f2() // warning C5245: 'f2': unreferenced function with internal linkage has been removed
{
return []{ return 13; };
}
Aviso sobre elisão de chave
No Visual Studio 2019 versão 16.10 e posteriores, o compilador avisa sobre listas de inicialização que não usam chaves para subobjetos. O compilador emite um aviso C5246 desativada por padrão.
Aqui está um exemplo:
struct S1 {
int i, j;
};
struct S2 {
S1 s1;
int k;
};
S2 s2{ 1, 2, 3 }; // warning C5246: 'S2::s1': the initialization of a subobject should be wrapped in braces
Para corrigir esse problema, encapsule a inicialização do subobjeto entre chaves:
S2 s2{ { 1, 2 }, 3 };
Detectar corretamente se um objeto const
não foi inicializado
No Visual Studio 2019 versão 16.10 e posteriores, o compilador agora emite o erro C2737 quando você tenta definir um objeto const
que não foi totalmente inicializado:
struct S {
int i;
int j = 2;
};
const S s; // error C2737: 's': const object must be initialized
Versões anteriores do compilador permitiam que esse código fosse compilado, mesmo que S::i
não esteja inicializado.
Para corrigir esse problema, inicialize todos os membros antes de criar uma instância de um objeto const
:
struct S {
int i = 1;
int j = 2;
};
Aprimoramentos de conformidade no Visual Studio 2019 versão 16.11
Modo do compilador /std:c++20
No Visual Studio 2019 versão 16.11 e posteriores, o compilador agora dá suporte ao modo /std:c++20
do compilador. Anteriormente, os recursos do C++20 estavam disponíveis apenas no modo /std:c++latest
no Visual Studio 2019. Os recursos do C++20 que originalmente exigiam o modo /std:c++latest
agora funcionam no modo /std:c++20
ou posterior, nas versões mais recentes do Visual Studio.