Compartilhar via


Aprimoramentos de conformidade do C++, alterações de comportamento e correções de bugs no Visual Studio 2017

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 ir diretamente para as alterações de uma versão específica, use a lista Neste artigo abaixo.

Este documento lista as alterações no Visual Studio 2017. Para ver um guia sobre as alterações no Visual Studio 2022, confira Aprimoramentos de conformidade do C++ no Visual Studio 2022. Para ver um guia sobre as alterações no Visual Studio 2019, confira Aprimoramentos de conformidade do C++ no Visual Studio 2019. Para ver uma lista completa dos aprimoramentos de conformidade anteriores, confira Visual C++, novidades de 2003 a 2015.

Aprimoramentos de conformidade no Visual Studio 2017 RTW (versão 15.0)

Com suporte para constexpr generalizado e NSDMI (inicialização de membros de dados não estáticos) para agregações, o compilador do MSVC no Visual Studio 2017 agora está completo com os recursos adicionados no padrão C++14. No entanto, o compilador ainda não tem alguns recursos dos padrões C++11 e C++98. Confira Conformidade da linguagem C/C++ da Microsoft para conhecer o estado atual do compilador.

C++11: suporte à expressão SFINAE em mais bibliotecas

O compilador continua a aprimorar seu suporte para a expressão SFINAE. É necessário para dedução e substituição de argumentos de modelo em que as expressões decltype e constexpr podem ser exibidas como parâmetros de modelo. Para obter mais informações, consulte Expression SFINAE improvements in Visual Studio 2017 RC (Melhorias da expressão SFINAE no Visual Studio 2017 RC).

C++14: NSDMI para agregações

Uma agregação é uma matriz ou uma classe que: não tem construtor fornecido pelo usuário, não tem membros de dados não estáticos que sejam privados ou protegidos, não tem classes base e não tem funções virtuais. A partir do C ++14, as agregações podem conter inicializadores de membro. Para obter mais informações, consulte Member initializers and aggregates (Inicializadores de membro e agregações).

C++14: constexpr estendido

Agora, as expressões declaradas como constexpr podem conter determinados tipos de declaração, instruções if e switch, declarações de loop e mutação de objetos cujo tempo de vida começou dentro da avaliação da expressão constexpr. Não há mais o requisito de que uma função de membro não estática constexpr seja implicitamente const. Para obter mais informações, confira Relaxamento de restrições em funções constexpr.

C++17: static_assert conciso

O parâmetro de mensagem para static_assert é opcional. Para obter mais informações, confira N3928: estendendo static_assert, v2.

C++17: atributo [[fallthrough]]

No modo /std:c++17 e posterior o atributo [[fallthrough]] pode ser usado no contexto de instruções switch como uma dica para o compilador de que o comportamento de fall-through é intencional. Esse atributo impede o compilador de emitir avisos em tais casos. Para obter mais informações, consulte P0188R0 - Wording for [[fallthrough]] attribute.

Loops for baseados em intervalo

Loops for baseados em intervalo não exigem mais que begin() e end() retornem objetos do mesmo tipo. Essa mudança permite que end() retorne uma sentinela conforme usado pelos intervalos em range-v3 e na Especificação Técnica de Intervalos concluída, mas não completamente publicada. Para obter mais informações, consulte P0184R0 - Generalizing the Range-Based for Loop.

Inicialização de lista de cópia

Visual Studio 2017 gera corretamente erros de compilador relacionados à criação de objetos usando listas de inicializadores. Esses erros não eram pegos no Visual Studio 2015 e podiam ocasionar falhas ou comportamento de runtime indefinido. De acordo com a N4594 13.3.1.7p1, na copy-list-initialization, o compilador deverá considerar um construtor explícito para resolução de sobrecarga. No entanto, ele deverá gerar um erro se essa sobrecarga específica for escolhida.

Os dois exemplos a seguir são compilados no Visual Studio 2015, mas não no Visual Studio 2017.

struct A
{
    explicit A(int) {}
    A(double) {}
};

int main()
{
    A a1 = { 1 }; // error C3445: copy-list-initialization of 'A' cannot use an explicit constructor
    const A& a2 = { 1 }; // error C2440: 'initializing': cannot convert from 'int' to 'const A &'

}

Para corrigir o erro, use a inicialização direta:

A a1{ 1 };
const A& a2{ 1 };

No Visual Studio 2015, o compilador tratava a inicialização de lista de cópia de maneira incorreta da mesma forma que a inicialização de cópia regular; ele considerava somente a conversão de construtores para a resolução de sobrecarga. No exemplo a seguir, o Visual Studio 2015 escolhe MyInt(23). O Visual Studio 2017 gera corretamente o erro.

// From http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_closed.html#1228
struct MyStore {
    explicit MyStore(int initialCapacity);
};

struct MyInt {
    MyInt(int i);
};

struct Printer {
    void operator()(MyStore const& s);
    void operator()(MyInt const& i);
};

void f() {
    Printer p;
    p({ 23 }); // C3066: there are multiple ways that an object
        // of this type can be called with these arguments
}

Esse exemplo é semelhante ao anterior, mas gera um erro diferente. Ele tem êxito no Visual Studio 2015 e falha no Visual Studio 2017 com C2668.

struct A {
    explicit A(int) {}
};

struct B {
    B(int) {}
};

void f(const A&) {}
void f(const B&) {}

int main()
{
    f({ 1 }); // error C2668: 'f': ambiguous call to overloaded function
}

Typedefs preteridos

Agora o Visual Studio 2017 emite o aviso correto para typedefs preteridos declarados em uma classe ou em um struct. O exemplo a seguir é compilado sem avisos no Visual Studio 2015. Ele produz o C4996 no Visual Studio 2017.

struct A
{
    // also for __declspec(deprecated)
    [[deprecated]] typedef int inttype;
};

int main()
{
    A::inttype a = 0; // C4996 'A::inttype': was declared deprecated
}

constexpr

O Visual Studio 2017 gera corretamente um erro quando o operando esquerdo de uma operação de avaliação condicional não é válido em um contexto constexpr. O código a seguir é compilado no Visual Studio 2015, mas não no Visual Studio 2017, pois gera o C3615:

template<int N>
struct array
{
    int size() const { return N; }
};

constexpr bool f(const array<1> &arr)
{
    return arr.size() == 10 || arr.size() == 11; // C3615 constexpr function 'f' cannot result in a constant expression
}

Para corrigir o erro, declare a função array::size() como constexpr ou remova o qualificador constexpr de f.

Tipos de classe passados para funções variadic

No Visual Studio 2017, classes ou structs passados para uma função variádica, como printf, devem ser facilmente copiados. Quando esses objetos são passados, o compilador simplesmente faz uma cópia bit a bit e não chama o construtor nem o destruidor.

#include <atomic>
#include <memory>
#include <stdio.h>

int main()
{
    std::atomic<int> i(0);
    printf("%i\n", i); // error C4839: non-standard use of class 'std::atomic<int>'
                        // as an argument to a variadic function.
                        // note: the constructor and destructor will not be called;
                        // a bitwise copy of the class will be passed as the argument
                        // error C2280: 'std::atomic<int>::atomic(const std::atomic<int> &)':
                        // attempting to reference a deleted function

    struct S {
        S(int i) : i(i) {}
        S(const S& other) : i(other.i) {}
        operator int() { return i; }
    private:
        int i;
    } s(0);
    printf("%i\n", s); // warning C4840 : non-portable use of class 'main::S'
                      // as an argument to a variadic function
}

Para corrigir o erro, é possível chamar uma função membro que retorna um tipo facilmente copiado,

    std::atomic<int> i(0);
    printf("%i\n", i.load());

ou usa uma conversão estática para converter o objeto antes de passá-lo:

    struct S {/* as before */} s(0);
    printf("%i\n", static_cast<int>(s))

Para cadeias de caracteres criadas e gerenciadas usando o CString, o operator LPCTSTR() fornecido deve ser usado para converter um objeto CString no ponteiro do C esperado pela cadeia de caracteres de formato.

CString str1;
CString str2 = _T("hello!");
str1.Format(_T("%s"), static_cast<LPCTSTR>(str2));

Qualificadores CV na construção de classe

No Visual Studio 2015, às vezes, o compilador ignora incorretamente o qualificador CV ao gerar um objeto de classe por meio de uma chamada do construtor. Eventualmente, esse problema pode gerar uma falha ou comportamento inesperado do runtime. O exemplo a seguir é compilado no Visual Studio 2015, mas gera um erro de compilador no Visual Studio 2017:

struct S
{
    S(int);
    operator int();
};

int i = (const S)0; // error C2440

Para corrigir o erro, declare operator int() como const.

Verificação de acesso em nomes qualificados em modelos

As versões anteriores do compilador não verificavam o acesso a nomes qualificados em alguns contextos de modelo. Esse problema pode interferir no comportamento esperado da SFINAE, em que a substituição deve falhar devido à inacessibilidade de um nome. Eventualmente, isso poderia ter causado uma falha ou comportamento inesperado no runtime, porque o compilador chamava incorretamente a sobrecarga errada do operador. No Visual Studio 2017, é gerado um erro do compilador. O erro específico pode variar, mas um erro típico é o C2672, "nenhuma função sobrecarregada correspondente encontrada". O código a seguir é compilado no Visual Studio 2015, mas gera um erro no Visual Studio 2017:

#include <type_traits>

template <class T> class S {
    typedef typename T type;
};

template <class T, std::enable_if<std::is_integral<typename S<T>::type>::value, T> * = 0>
bool f(T x);

int main()
{
    f(10); // C2672: No matching overloaded function found.
}

Listas de argumentos de modelo ausentes

No Visual Studio 2015 e anteriores, o compilador não diagnosticava todas as listas de argumentos de modelo ausentes. Ele não observaria quando o modelo ausente aparecesse em uma lista de parâmetros de modelo: por exemplo, quando parte de um argumento de modelo padrão ou um parâmetro de modelo não tipo estivesse ausente. Esse problema pode resultar em comportamento imprevisível, incluindo falhas do compilador ou comportamento inesperado do runtime. O código a seguir é compilado no Visual Studio 2015, mas produz um erro no Visual Studio 2017.

template <class T> class ListNode;
template <class T> using ListNodeMember = ListNode<T> T::*;
template <class T, ListNodeMember M> class ListHead; // C2955: 'ListNodeMember': use of alias
                                                     // template requires template argument list

// correct:  template <class T, ListNodeMember<T> M> class ListHead;

Expressão SFINAE

Para dar suporte à expressão SFINAE, agora o compilador analisa argumentos decltype quando os modelos são declarados em vez de instanciados. Portanto, se uma especialização não dependente for encontrada no argumento decltype, ela não será adiada até a hora da instanciação. Ela será processada imediatamente, e qualquer erro resultante será diagnosticado nesse momento.

O exemplo a seguir mostra esse erro do compilador gerado no momento da declaração:

#include <utility>
template <class T, class ReturnT, class... ArgsT>
class IsCallable
{
public:
    struct BadType {};

    template <class U>
    static decltype(std::declval<T>()(std::declval<ArgsT>()...)) Test(int); //C2064. Should be declval<U>

    template <class U>
    static BadType Test(...);

    static constexpr bool value = std::is_convertible<decltype(Test<T>(0)), ReturnT>::value;
};

constexpr bool test1 = IsCallable<int(), int>::value;
static_assert(test1, "PASS1");
constexpr bool test2 = !IsCallable<int*, int>::value;
static_assert(test2, "PASS2");

Classes declaradas em namespaces anônimos

De acordo com o padrão C++, uma classe declarada dentro de um namespace anônimo tem vinculação interna, e isso significa que ela não pode ser exportada. No Visual Studio 2015 e anteriores, essa regra não era aplicada. No Visual Studio 2017, a regra é parcialmente aplicada. No Visual Studio 2017, o exemplo a seguir gera o erro C2201:

struct __declspec(dllexport) S1 { virtual void f() {} };
  // C2201 const anonymous namespace::S1::vftable: must have external linkage
  // in order to be exported/imported.

Inicializadores padrão para membros de classe de valor (C++/CLI)

No Visual Studio 2015 e nas versões anteriores, o compilador permitia (mas ignorava) um inicializador de membro padrão para um membro de uma classe de valor. A inicialização padrão de uma classe de valor sempre inicializa os membros em zero. Não é permitido um construtor padrão. No Visual Studio 2017, os inicializadores de membro padrão geram um erro de compilador, conforme mostrado neste exemplo:

value struct V
{
    int i = 0; // error C3446: 'V::i': a default member initializer
               // isn't allowed for a member of a value class
};

Indexadores padrão (C++/CLI)

No Visual Studio 2015 e em versões anteriores, o compilador, em alguns casos, identificava incorretamente uma propriedade padrão como um indexador padrão. É possível resolver o problema usando o identificador default para acessar a propriedade. A solução em si se tornou um problema depois que default foi introduzido como uma palavra-chave no C++ 11. No Visual Studio 2017, os bugs que exigiam a solução alternativa foram corrigidos. Agora o compilador gera um erro quando o default é usado para acessar a propriedade padrão de uma classe.

//class1.cs

using System.Reflection;
using System.Runtime.InteropServices;

namespace ClassLibrary1
{
    [DefaultMember("Value")]
    public class Class1
    {
        public int Value
        {
            // using attribute on the return type triggers the compiler bug
            [return: MarshalAs(UnmanagedType.I4)]
            get;
        }
    }
    [DefaultMember("Value")]
    public class Class2
    {
        public int Value
        {
            get;
        }
    }
}

// code.cpp
#using "class1.dll"

void f(ClassLibrary1::Class1 ^r1, ClassLibrary1::Class2 ^r2)
{
       r1->Value; // error
       r1->default;
       r2->Value;
       r2->default; // error
}

No Visual Studio de 2017, é possível acessar ambas as propriedades Value pelo nome:

#using "class1.dll"

void f(ClassLibrary1::Class1 ^r1, ClassLibrary1::Class2 ^r2)
{
       r1->Value;
       r2->Value;
}

Aprimoramentos de conformidade na versão 15.3

Lambdas constexpr

Expressões Lambda agora podem ser usadas em expressões de constante. Para obter mais informações, confira Expressões lambda constexpr no C++.

if constexpr em modelos de função

Um modelo de função pode conter instruções if constexpr para habilitar a ramificação de tempo de compilação. Para obter mais informações, confira Instruções if constexpr.

Instruções de seleção com inicializadores

Uma instrução if pode conter um inicializador que apresenta uma variável no escopo de bloco dentro da instrução em si. Para mais informações, confira Instruções if com inicializador.

Atributos [[maybe_unused]] e [[nodiscard]]

O novo atributo [[maybe_unused]] silencia avisos quando uma entidade não é usada. O atributo [[nodiscard]] criará um aviso se o valor retornado de uma chamada de função for descartado. Para saber mais, veja Atributos em C++.

Uso de namespaces de atributo sem repetição

Nova sintaxe para permitir apenas um único identificador de namespace em uma lista de atributos. Para saber mais, veja Atributos em C++.

Associações estruturadas

Agora é possível, em uma única declaração, armazenar um valor com nomes individuais de seus componentes, quando o valor é uma matriz, um std::tuple ou std::pair, ou tem todos os membros de dados não estáticos públicos. Para saber mais, veja P0144R0 - Structured Bindings e Retornar vários valores de uma função.

Regras de construção para valores enum class

Agora há uma conversão implícita não restritiva para enumerações com escopo. Ela converte o tipo subjacente de uma enumeração com escopo na própria enumeração. A conversão fica disponível quando sua definição não introduz um enumerador e quando a origem usa uma sintaxe de inicialização de lista. Para obter mais informações, confira P0138R2 - Construction Rules for enum class Values e Enumerações.

Capturar *this por valor

O objeto *this em uma expressão lambda agora pode ser capturado por valor. Essa mudança permite cenários nos quais o lambda é invocado em operações paralelas e assíncronas, especialmente em arquiteturas de computadores mais recentes. Para obter mais informações, consulte P0018R3 - Lambda Capture of *this by Value as [=,*this].

Remover operator++ para bool

operator++ não é mais compatível em tipos bool. Para obter mais informações, consulte P0002R1 - Remove Deprecated operator++(bool).

Remover a palavra-chave register preterida

A palavra-chave register, anteriormente preterida (e ignorada pelo compilador), agora foi removida da linguagem. Para obter mais informações, consulte P0001R1 - Remove Deprecated Use of the register Keyword.

Chamadas para modelos de membros excluídos

Em alguns casos, nas versões anteriores do Visual Studio, o compilador falhava ao emitir um erro para chamadas malformados de um modelo de membro excluído. Essas chamadas poderiam causar falhas em runtime. Agora, o código a seguir produz o erro C2280:

template<typename T>
struct S {
   template<typename U> static int f() = delete;
};

void g()
{
   decltype(S<int>::f<int>()) i; // this should fail with
// C2280: 'int S<int>::f<int>(void)': attempting to reference a deleted function
}

Para corrigir o erro, declare i como int.

Verificações de pré-condição para características de tipo

O Visual Studio 2017 versão 15.3 melhora as verificações de pré-condição para características de tipo para seguir o padrão mais estritamente. Uma verificação assim destina-se aos atribuíveis. O código a seguir produz o C2139 no Visual Studio 2017 versão 15.3:

struct S;
enum E;

static_assert(!__is_assignable(S, S), "fail"); // C2139 in 15.3
static_assert(__is_convertible_to(E, E), "fail"); // C2139 in 15.3

Novas verificações de runtime e o aviso do compilador em marshaling nativo para gerenciado

Chamar desde funções gerenciadas a funções nativas requer o marshaling. O CLR faz marshaling, mas não entende a semântica do C++. Se você passar um objeto nativo por valor, o CLR chamará o construtor de cópia do objeto ou usará BitBlt, o que poderá causar um comportamento indefinido em runtime.

Agora o compilador emitirá um aviso se encontrar esse erro em tempo de compilação: um objeto nativo com o construtor de cópia excluído é passado entre um limite nativos e gerenciado por valor. Nos casos em que o compilador não souber no tempo de compilação, ele injetará uma verificação de runtime para que o programa chame std::terminate imediatamente quando um marshaling malformado ocorrer. No Visual Studio 2017 versão 15.3, o seguinte código produz o aviso C4606:

class A
{
public:
   A() : p_(new int) {}
   ~A() { delete p_; }

   A(A const &) = delete;
   A(A &&rhs) {
   p_ = rhs.p_;
}

private:
   int *p_;
};

#pragma unmanaged

void f(A a)
{
}

#pragma managed

int main()
{
    // This call from managed to native requires marshaling. The CLR doesn't
    // understand C++ and uses BitBlt, which results in a double-free later.
    f(A()); // C4606 'A': passing argument by value across native and managed
    // boundary requires valid copy constructor. Otherwise, the runtime
    // behavior is undefined.`
}

Para corrigir o erro, remova a diretiva #pragma managed para marcar o chamador como nativo e evitar marshaling.

Aviso de API experimental para WinRT

As APIs do WinRT lançadas para experimentação e comentários são decoradas com Windows.Foundation.Metadata.ExperimentalAttribute. No Visual Studio 2017 versão 15.3, o compilador gera o aviso C4698 para esse atributo. Algumas APIs em versões anteriores do SDK do Windows já tinham sido decoradas com o atributo, e chamadas para essas APIs agora começam a disparar esse aviso do compilador. Os SDKs mais recentes do Windows têm o atributo removido de todos os tipos enviados. Se estiver usando um SDK mais antigo, você precisará suprimir esses avisos de todas as chamadas para tipos enviados.

O código a seguir produz o aviso C4698:

Windows::Storage::IApplicationDataStatics2::GetForUserAsync(); // C4698
// 'Windows::Storage::IApplicationDataStatics2::GetForUserAsync' is for
// evaluation purposes only and is subject to change or removal in future updates

Para desabilitar o aviso, adicione um #pragma:

#pragma warning(push)
#pragma warning(disable:4698)

Windows::Storage::IApplicationDataStatics2::GetForUserAsync();

#pragma warning(pop)

Definição fora da linha de uma função de membro de modelo

O Visual Studio 2017 versão 15.3 produz um erro para uma definição fora da linha de uma função de membro de modelo que não foi declarada na classe. Agora, o seguinte código produz o erro C2039:

struct S {};

template <typename T>
void S::f(T t) {} // C2039: 'f': is not a member of 'S'

Para corrigir o erro, adicione uma declaração à classe:

struct S {
    template <typename T>
    void f(T t);
};
template <typename T>
void S::f(T t) {}

Tentar obter o endereço do ponteiro this

No C++, this é um prvalue do tipo ponteiro para X. Você não pode obter o endereço de this nem associá-lo a uma referência de lvalue. Nas versões anteriores do Visual Studio, o compilador permitia contornar essa restrição com o uso de uma conversão. No Visual Studio 2017 versão 15.3, o compilador produz o erro C2664.

Conversão em uma classe base inacessível

O Visual Studio 2017 versão 15.3 gera um erro quando você tenta converter um tipo em uma classe base que está inacessível. O código a seguir está malformado e poderá causar uma falha em runtime. Agora, o compilador produz o C2243 ao encontrar código como este:

#include <memory>

class B { };
class D : B { }; // C2243: 'type cast': conversion from 'D *' to 'B *' exists, but is inaccessible

void f()
{
   std::unique_ptr<B>(new D());
}

Os argumentos padrão não são permitidos em definições fora de linha de funções de membro

Os argumentos padrão não são permitidos em definições fora da linha de funções de membro em classes de modelo. O compilador emitirá um aviso em /permissive e um erro de hardware em /permissive-.

Nas versões anteriores do Visual Studio, o código malformado a seguir pode causar uma falha de runtime. O Visual Studio 2017 versão 15.3 produz o aviso C5037:

template <typename T>
struct A {
    T f(T t, bool b = false);
};

template <typename T>
T A<T>::f(T t, bool b = false) // C5037: 'A<T>::f': an out-of-line definition of a member of a class template cannot have default arguments
{
    // ...
}

Para corrigir o erro, remova o argumento padrão = false.

Uso de offsetof com o designador de membro composto

No Visual Studio 2017 versão 15.3, usar offsetof(T, m), em que m é um "designador de membro composto", resulta em um aviso quando você compila com a opção /Wall. O código a seguir está malformado e pode causar falhas em runtime. O Visual Studio 2017 versão 15.3 produz o aviso C4841:

struct A {
   int arr[10];
};

// warning C4841: non-standard extension used: compound member designator used in offsetof
constexpr auto off = offsetof(A, arr[2]);

Para corrigir o código, desabilite o aviso com um pragma ou altere o código para não usar offsetof:

#pragma warning(push)
#pragma warning(disable: 4841)
constexpr auto off = offsetof(A, arr[2]);
#pragma warning(pop)

Uso de offsetof com o membro de dados estáticos ou função de membro

No Visual Studio 2017 versão 15.3, usar offsetof(T, m), em que m se refere a um membro de dados estático ou uma função de membro, resultará em um erro. O seguinte código produz o erro C4597:

#include <cstddef>

struct A {
   int ten() { return 10; }
   static constexpr int two = 2;
};

constexpr auto off = offsetof(A, ten);  // C4597: undefined behavior: offsetof applied to member function 'A::ten'
constexpr auto off2 = offsetof(A, two); // C4597: undefined behavior: offsetof applied to static data member 'A::two'

Esse código está malformado e pode causar falhas em runtime. Para corrigir o erro, altere o código para não invocar mais um comportamento indefinido. Esse é o código não portátil que não é permitido pelo padrão C++.

Novo aviso em atributos __declspec

No Visual Studio 2017 versão 15.3, o compilador não ignora os atributos se __declspec(...) é aplicado antes da especificação de vinculação extern "C". Anteriormente, o compilador ignorava o atributo, o que podia ter implicações de runtime. Quando as opções /Wall e /WX são definidas, o seguinte código produz o aviso C4768:

__declspec(noinline) extern "C" HRESULT __stdcall // C4768: __declspec attributes before linkage specification are ignored

Para corrigir o aviso, coloque extern "C" primeiro:

extern "C" __declspec(noinline) HRESULT __stdcall

Esse aviso está desativado por padrão no Visual Studio 2017 versão 15.3 e só afeta o código compilado com /Wall /WX. A partir do Visual Studio 2017 versão 15.5, ele fica habilitado por padrão como um aviso de nível 3.

decltype e chamadas para destruidores excluídos

Nas versões anteriores do Visual Studio, o compilador não detectava quando ocorria uma chamada para um destruidor excluído no contexto de expressão associada a decltype. No Visual Studio 2017 versão 15.3, o seguinte código produz o erro C2280:

template<typename T>
struct A
{
   ~A() = delete;
};

template<typename T>
auto f() -> A<T>;

template<typename T>
auto g(T) -> decltype((f<T>()));

void h()
{
   g(42); // C2280: 'A<T>::~A(void)': attempting to reference a deleted function
}

Variáveis constantes não inicializadas

O Visual Studio 2017 RTW teve uma regressão: o compilador do C++ não emitia um diagnóstico para uma variável const não inicializada. Essa regressão foi corrigida no Visual Studio 2017 versão 15.3. O seguinte código agora produz o aviso C4132:

const int Value; // C4132: 'Value': const object should be initialized

Para corrigir o erro, atribua um valor a Value.

Declarações vazias

O Visual Studio 2017 versão 15.3 agora avisa sobre declarações vazias para todos os tipos, não apenas tipos internos. Agora, o seguinte código produz um aviso C4091 de nível 2 para todas as quatro declarações:

struct A {};
template <typename> struct B {};
enum C { c1, c2, c3 };

int;    // warning C4091 : '' : ignored on left of 'int' when no variable is declared
A;      // warning C4091 : '' : ignored on left of 'main::A' when no variable is declared
B<int>; // warning C4091 : '' : ignored on left of 'B<int>' when no variable is declared
C;      // warning C4091 : '' : ignored on left of 'C' when no variable is declared

Para remover os avisos, comente ou remova as declarações vazias. Em casos em que o objeto não nomeado se destinar a ter um efeito colateral (como RAII), ele deverá receber um nome.

O aviso é excluído em /Wv:18 e é ativado por padrão no nível de aviso W2.

std::is_convertible para tipos de matriz

As versões anteriores do compilador forneciam resultados incorretos para std::is_convertiblepara tipos de matriz. Isso exigia que gravadores de biblioteca tratassem o compilador do Microsoft C++ de forma especial ao usar a característica de tipo std::is_convertible<...>. No exemplo a seguir, as declarações de estática passam em versões anteriores do Visual Studio, mas falham no Visual Studio 2017 versão 15.3:

#include <type_traits>

using Array = char[1];

static_assert(std::is_convertible<Array, Array>::value);
static_assert(std::is_convertible<const Array, const Array>::value, "");
static_assert(std::is_convertible<Array&, Array>::value, "");
static_assert(std::is_convertible<Array, Array&>::value, "");

std::is_convertible<From, To> é calculada para verificar se uma definição de função imaginária está bem formada:

   To test() { return std::declval<From>(); }

Destruidores privados e std::is_constructible

As versões anteriores do compilador ignoravam se um destruidor era particular quando decidia o resultado de std::is_constructible. Agora ele os considera. No exemplo a seguir, as declarações de estática passam em versões anteriores do Visual Studio, mas falham no Visual Studio 2017 versão 15.3:

#include <type_traits>

class PrivateDtor {
   PrivateDtor(int) { }
private:
   ~PrivateDtor() { }
};

// This assertion used to succeed. It now correctly fails.
static_assert(std::is_constructible<PrivateDtor, int>::value);

Os destruidores particulares fazem com que um tipo não seja construível. std::is_constructible<T, Args...> é calculada como se a declaração a seguir estivesse escrita:

   T obj(std::declval<Args>()...)

Essa chamada implica em uma chamada de destruidor.

C2668: resolução de sobrecarga ambígua

As versões anteriores do compilador falhavam algumas vezes ao detectar ambiguidade quando encontravam vários candidatos, tanto por meio de declarações using quanto de pesquisas dependentes de argumento. Essa falha pode levar à escolha errada de sobrecarga e ao comportamento inesperado de runtime. No seguinte exemplo, o Visual Studio 2017 versão 15.3 gera corretamente o C2668:

namespace N {
   template<class T>
   void f(T&, T&);

   template<class T>
   void f();
}

template<class T>
void f(T&, T&);

struct S {};
void f()
{
   using N::f;

   S s1, s2;
   f(s1, s2); // C2668: 'f': ambiguous call to overloaded function
}

Para corrigir o código, remova o uso da instrução N::f se você pretende chamar ::f().

C2660: declarações de função locais e pesquisa dependente de argumento

As declarações de função local ocultam a declaração de função no escopo delimitador e desabilitam a pesquisa dependente de argumento. As versões anteriores do compilador sempre faziam uma pesquisa dependente de argumentos nesse caso. Isso poderia levar a um comportamento de runtime inesperado, se o compilador escolhesse a sobrecarga errada. Normalmente, o erro é devido a uma assinatura incorreta da declaração da função local. No seguinte exemplo, o Visual Studio 2017 versão 15.3 gera corretamente o C2660:

struct S {};
void f(S, int);

void g()
{
   void f(S); // C2660 'f': function does not take 2 arguments:
   // or void f(S, int);
   S s;
   f(s, 0);
}

Para corrigir o problema, altere a assinatura f(S) ou remova-a.

C5038: ordem de inicialização em listas de inicializador

Os membros de classe são inicializados na ordem em que são declarados e não na ordem em que aparecem em listas de inicializadores. As versões anteriores do compilador não avisavam quando a ordem da lista de inicializadores era diferente da ordem da declaração. Esse problema poderia resultar em comportamento indefinido de runtime, caso a inicialização de um membro dependesse de outro membro na lista que já estivesse sendo inicializada. No seguinte exemplo, o Visual Studio 2017 versão 15.3 (com /Wall) gera o aviso C5038:

struct A
{    // Initialized in reverse, y reused
    A(int a) : y(a), x(y) {} // C5038: data member 'A::y' will be initialized after data member 'A::x'
    int x;
    int y;
};

Para corrigir o problema, organize a lista do inicializador para que tenha a mesma ordem que as declarações. Um aviso semelhante é gerado quando um ou ambos os inicializadores fazem referência a membros de classe base.

Esse aviso está desativado por padrão e afeta somente o código compilado com /Wall.

Aprimoramentos de conformidade na versão 15.5

Recursos marcados com [14] estão disponíveis incondicionalmente, mesmo no modo /std:c++14.

Nova opção de compilador para extern constexpr

Em versões anteriores do Visual Studio, o compilador sempre forneceu um vínculo interno variável constexpr, mesmo quando a variável era marcada como extern. No Visual Studio 2017 versão 15.5, uma nova opção de compilador (/Zc:externConstexpr) habilita o comportamento correto em conformidade com os padrões. Para obter mais informações, confira vínculo extern constexpr.

Remover especificações de exceção dinâmica

P0003R5 As especificações de exceções dinâmicas foram preteridas no C++11. O recurso foi removido do C++17, mas a especificação throw() (ainda) preterida é mantida estritamente como um alias para noexcept(true). Para obter mais informações, confira Remoção de especificação de exceção dinâmica e noexcept.

not_fn()

P0005R4 not_fn é uma substituição de not1 e not2.

Reescrever enable_shared_from_this

P0033R1 enable_shared_from_this foi adicionada no C++ 11. O padrão C++17 atualiza a especificação para lidar melhor com certos casos de canto. [14]

Unir mapas e conjuntos

P0083R3 Esse recurso permite a extração de nós de contêineres associativos (isto é, map, set, unordered_map, unordered_set) que podem ser modificados e inseridos de volta no mesmo contêiner ou em um contêiner diferente que use o mesmo tipo de nó. (É um caso de uso comum extrair um nó de um std::map, alterar a chave e inseri-lo novamente.)

Preterir partes residuais da biblioteca

P0174R2 Vários recursos da biblioteca padrão do C++ foram substituídos por novos recursos ao longo dos anos, ou porque não se mostraram úteis, ou porque apresentaram problemas. Esses recursos foram oficialmente preteridos no C++17.

Remover o suporte ao alocador em std::function

P0302R1 Antes do C++17, o modelo de classe std::function tinha vários construtores que usavam um argumento de alocador. No entanto, o uso de alocadores neste contexto era problemático e a semântica era confusa. Os construtores de problema foram removidos.

Correções para not_fn()

P0358R1 A nova expressão para std::not_fn dão suporte à propagação de categoria de valor quando usada na invocação do wrapper.

shared_ptr<T[]>, shared_ptr<T[N]>

P0414R2 Mesclando alterações do shared_ptr dos Princípios básicos de biblioteca para o C++17. [14]

Corrigir shared_ptr para matrizes

P0497R0 Correções para o suporte de shared_ptr para matrizes. [14]

Esclarecer insert_return_type

P0508R0 Os contêineres associativos e não ordenados, ambos com chaves exclusivas, têm uma função de membro insert que retorna um tipo aninhado insert_return_type. O tipo de retorno agora é definido como uma especialização de um tipo com parâmetros no iterador e NodeType do contêiner.

Variáveis embutidas para a biblioteca padrão

Para P0607R0, várias variáveis comuns declaradas na biblioteca padrão agora são declaradas embutidas.

Recursos preteridos do anexo D

O anexo D do C++ padrão contém todos os recursos que foram preteridos, incluindo shared_ptr::unique(), <codecvt> e namespace std::tr1. Quando a opção do compilador /std:c++17 ou posterior está definida, quase todos os recursos da biblioteca padrão do Anexo D são marcados como preteridos. Para saber mais, confira Recursos da biblioteca padrão no Anexo D são marcados como preteridos.

O namespace std::tr2::sys em <experimental/filesystem> agora emite um aviso de substituição em /std:c++14 por padrão e agora será removido em /std:c++17 e posteriores por padrão.

Melhoria na conformidade em <iostream>, evitando uma extensão não padrão (especializações explícitas na classe).

A biblioteca padrão agora usa modelos de variável internamente.

A biblioteca padrão foi atualizada em resposta às alterações do compilador do C++17. As atualizações incluem a adição de noexcept no sistema de tipos e a remoção de especificações de exceção dinâmica.

Alteração de ordenação parcial

O compilador agora rejeita corretamente o código a seguir e oferece a mensagem de erro correta:

template<typename... T>
int f(T* ...)
{
    return 1;
}

template<typename T>
int f(const T&)
{
    return 2;
}

int main()
{
    int i = 0;
    f(&i);    // C2668
}
t161.cpp
t161.cpp(16): error C2668: 'f': ambiguous call to overloaded function
t161.cpp(8): note: could be 'int f<int*>(const T &)'
        with
        [
            T=int*
        ]
t161.cpp(2): note: or       'int f<int>(int*)'
t161.cpp(16): note: while trying to match the argument list '(int*)'

O problema no exemplo acima é que há duas diferenças nos tipos (const versus não const e pacote versus não pack). Para eliminar o erro do compilador, remova uma das diferenças. Em seguida, o compilador poderá ordenar as funções de maneira inequívoca.

template<typename... T>
int f(T* ...)
{
    return 1;
}

template<typename T>
int f(T&)
{
    return 2;
}

int main()
{
    int i = 0;
    f(&i);
}

Manipuladores de exceção

Manipuladores de referência para o tipo de matriz ou função nunca são uma correspondência para qualquer objeto de exceção. Agora, o compilador segue corretamente essa regra e gera um aviso de nível 4, C4843. Ele também não faz mais a correspondência de um manipulador de char* ou wchar_t* com um literal de cadeia de caracteres quando /Zc:strictStrings é usado.

int main()
{
    try {
        throw "";
    }
    catch (int (&)[1]) {} // C4843 (This should always be dead code.)
    catch (void (&)()) {} // C4843 (This should always be dead code.)
    catch (char*) {} // This should not be a match under /Zc:strictStrings
}
warning C4843: 'int (&)[1]': An exception handler of reference to array or function type is unreachable, use 'int*' instead
warning C4843: 'void (__cdecl &)(void)': An exception handler of reference to array or function type is unreachable, use 'void (__cdecl*)(void)' instead

O código a seguir evita o erro:

catch (int (*)[1]) {}

O namespace std::tr1 é preterido

O namespace não padrão std::tr1 está marcado como preterido nos modos C++14 e C++17. No Visual Studio 2017 versão 15.5, o código a seguir aciona C4996:

#include <functional>
#include <iostream>
using namespace std;

int main() {
    std::tr1::function<int (int, int)> f = std::plus<int>(); //C4996
    cout << f(3, 5) << std::endl;
    f = std::multiplies<int>();
    cout << f(3, 5) << std::endl;
}
warning C4996: 'std::tr1': warning STL4002: The non-standard std::tr1 namespace and TR1-only machinery are deprecated and will be REMOVED. You can define _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING to acknowledge that you have received this warning.

Para corrigir o erro, remova a referência ao namespace tr1:

#include <functional>
#include <iostream>
using namespace std;

int main() {
    std::function<int (int, int)> f = std::plus<int>();
    cout << f(3, 5) << std::endl;
    f = std::multiplies<int>();
    cout << f(3, 5) << std::endl;
}

Os recursos da biblioteca padrão no Anexo D são marcados como preteridos

Quando a opção do compilador do modo /std:c++17 ou posterior é definida, quase todos os recursos da biblioteca padrão do Anexo D são marcados como preteridos.

No Visual Studio 2017 versão 15.5, o código a seguir aciona C4996:

#include <iterator>

class MyIter : public std::iterator<std::random_access_iterator_tag, int> {
public:
    // ... other members ...
};

#include <type_traits>

static_assert(std::is_same<MyIter::pointer, int*>::value, "BOOM");
warning C4996: 'std::iterator<std::random_access_iterator_tag,int,ptrdiff_t,_Ty*,_Ty &>::pointer': warning STL4015: The std::iterator class template (used as a base class to provide typedefs) is deprecated in C++17. (The <iterator> header is NOT deprecated.) The C++ standard has never required user-defined iterators to derive from std::iterator. To fix this warning, stop deriving from std::iterator and start providing publicly accessible typedefs named iterator_category, value_type, difference_type, pointer, and reference. Note that value_type is required to be non-const, even for constant iterators. You can define _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING or _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS to acknowledge that you have received this warning.

Para corrigir o erro, siga as instruções no texto de aviso, conforme demonstrado no código a seguir:

#include <iterator>

class MyIter {
public:
    typedef std::random_access_iterator_tag iterator_category;
    typedef int value_type;
    typedef ptrdiff_t difference_type;
    typedef int* pointer;
    typedef int& reference;

    // ... other members ...
};

#include <type_traits>

static_assert(std::is_same<MyIter::pointer, int*>::value, "BOOM");

Variáveis locais não referenciadas

No Visual Studio 15.5, o aviso C4189 é emitido em mais casos, conforme mostrado no seguinte código:

void f() {
    char s[2] = {0}; // C4189. Either use the variable or remove it.
}
warning C4189: 's': local variable is initialized but not referenced

Para corrigir o erro, remova a variável não usada.

Comentários de única linha

No Visual Studio 2017 versão 15.5, os avisos C4001 e C4179 não são emitidos pelo compilador C. Anteriormente, eles somente eram emitidos na opção do compilador /Za. Os avisos não são mais necessários porque comentários de linha única fazem parte do padrão C desde C99.

/* C only */
#pragma warning(disable:4001) // C4619
#pragma warning(disable:4179)
// single line comment
//* single line comment */
warning C4619: #pragma warning: there is no warning number '4001'

Se o código não precisar ser compatível com versões anteriores, evite o aviso removendo a supressão de C4001 e C4179. Se o código precisar ser compatível com versões anteriores, suprima somente C4619.

/* C only */

#pragma warning(disable:4619)
#pragma warning(disable:4001)
#pragma warning(disable:4179)

// single line comment
/* single line comment */

Atributos __declspec com vinculação extern "C"

Em versões anteriores do Visual Studio, o compilador ignorou atributos __declspec(...) quando __declspec(...) foi aplicado antes da especificação de vinculação extern "C". Esse comportamento faz com que seja gerado código não pretendido pelo usuário, com possíveis consequências no runtime. O aviso C4768 foi adicionado no Visual Studio versão 15.3, mas estava desativado por padrão. No Visual Studio 2017 versão 15.5, o aviso está habilitado por padrão.

__declspec(noinline) extern "C" HRESULT __stdcall // C4768
warning C4768: __declspec attributes before linkage specification are ignored

Para corrigir o erro, coloque a especificação de vinculação antes do atributo __declspec:

extern "C" __declspec(noinline) HRESULT __stdcall

Esse novo aviso C4768 é determinado em alguns cabeçalhos do SDK do Windows que foram fornecidos com o Visual Studio 2017 15.3 ou anterior (por exemplo: versão 10.0.15063.0, também conhecido como SDK do RS2). No entanto, as versões posteriores dos cabeçalhos do SDK do Windows (especificamente, ShlObj.h e ShlObj_core.h) foram corrigidas para que não produzam o aviso. Quando você vir esse aviso proveniente de cabeçalhos do SDK do Windows, poderá executar estas ações:

  1. Mude para o SDK do Windows mais recente que acompanha o Visual Studio 2017 versão 15.5.

  2. Desligue o aviso ao redor de #include da instrução de cabeçalho do SDK do Windows:

   #pragma warning (push)
   #pragma warning(disable:4768)
   #include <shlobj.h>
   #pragma warning (pop)

Vinculação de extern constexpr

Em versões anteriores do Visual Studio, o compilador sempre forneceu uma ligação interna variável constexpr, mesmo quando a variável era marcada como extern. No Visual Studio 2017 versão 15.5, uma nova opção de compilador (/Zc:externConstexpr) habilita o comportamento correto e em conformidade com os padrões. No fim das contas, esse comportamento se tornará o padrão.

extern constexpr int x = 10;
error LNK2005: "int const x" already defined

Se um arquivo de cabeçalho contiver uma variável declarada extern constexpr, ele precisará ser marcado como __declspec(selectany) para que suas declarações duplicadas sejam combinadas corretamente:

extern constexpr __declspec(selectany) int x = 10;

typeid não pode ser usado em tipo de classe incompleto

Em versões anteriores do Visual Studio, o compilador permitia incorretamente o código a seguir, resultando em informações de tipo potencialmente incorretas. No Visual Studio 2017 versão 15.5, o compilador produz corretamente um erro:

#include <typeinfo>

struct S;

void f() { typeid(S); } //C2027 in 15.5
error C2027: use of undefined type 'S'

Tipo de destino de std::is_convertible

std::is_convertible requer que o tipo de destino seja um tipo de retorno válido. Em versões anteriores do Visual Studio, o compilador permitia incorretamente tipos abstratos, que podem levar à resolução de sobrecarga incorreta e ao comportamento de runtime imprevisto. O código a seguir agora aciona C2338 corretamente:

#include <type_traits>

struct B { virtual ~B() = 0; };
struct D : public B { virtual ~D(); };

static_assert(std::is_convertible<D, B>::value, "fail"); // C2338 in 15.5

Para evitar o erro, você deve comparar tipos de ponteiro ao usar is_convertible, pois uma comparação de tipo que não é de ponteiro poderá falhar se um tipo for abstrato:

#include <type_traits>

struct B { virtual ~B() = 0; };
struct D : public B { virtual ~D(); };

static_assert(std::is_convertible<D *, B *>::value, "fail");

Remoção de especificação de exceção dinâmica e noexcept

No C++ 17, throw() é um alias para noexcept, throw(<type list>) e throw(...) foram removidos e determinados tipos podem incluir noexcept. Essa mudança pode causar problemas de compatibilidade de origem com o código que está em conformidade com a C++14 ou anterior. A opção /Zc:noexceptTypes- pode ser usada para reverter para a versão C++14 do noexcept ao usar o modo C++17 em geral. Ela permite atualizar seu código-fonte para estar em conformidade com a C++17 sem precisar reescrever todo o código throw() ao mesmo tempo.

O compilador agora também diagnostica mais especificações de exceção incompatíveis em declarações no modo C++17 ou com /permissive- com o novo aviso C5043.

O seguinte código gera C5043 e C5040 no Visual Studio 2017 versão 15.5 quando a opção /std:c++17 é aplicada:

void f() throw(); // equivalent to void f() noexcept;
void f() {} // warning C5043
void g() throw(); // warning C5040

struct A {
    virtual void f() throw();
};

struct B : A {
    virtual void f() { } // error C2694
};

Para remover os erros enquanto estiver usando /std:c++17, adicione a opção /Zc:noexceptTypes- à linha de comando ou atualize o código para usar noexcept, conforme mostrado no seguinte exemplo:

void f() noexcept;
void f() noexcept { }
void g() noexcept(false);

struct A {
    virtual void f() noexcept;
};

struct B : A {
    virtual void f() noexcept { }
};

Variáveis embutidas

Membros de dados constexpr estáticos agora são implicitamente inline, o que significa que sua declaração em uma classe agora é sua definição. O uso de uma definição fora de linha para um membro de dados static constexpr estático é redundante e agora está preterido. No Visual Studio 2017 versão 15.5, quando a opção /std:c++17 está aplicada, o seguinte código agora produz o aviso C5041:

struct X {
    static constexpr int size = 3;
};
const int X::size; // C5041: 'size': out-of-line definition for constexpr static data member is not needed and is deprecated in C++17

O aviso C4768 de extern "C" __declspec(...) agora está ativado por padrão

O aviso foi adicionado no Visual Studio versão 2017 versão 15.3, mas estava desativado por padrão. No Visual Studio 2017 versão 15.5, o aviso está ativo por padrão. Para saber mais, confira Novo aviso sobre atributos __declspec.

Funções usadas como padrão e __declspec(nothrow)

O compilador permitia anteriormente que funções padronizadas fossem declaradas com __declspec(nothrow) quando as funções de base/membro correspondente permitiam exceções. Esse comportamento é contrário ao padrão C++ e pode causar um comportamento indefinido no runtime. O padrão exige que tais funções sejam definidas como excluídas se houver uma incompatibilidade de especificação de exceção. No /std:c++17, o seguinte código gera o C2280:

struct A {
    A& operator=(const A& other) { // No exception specification; this function may throw.
        ...
    }
};

struct B : public A {
    __declspec(nothrow) B& operator=(const B& other) = default;
};

int main()
{
    B b1, b2;
    b2 = b1; // error C2280: attempting to reference a deleted function.
             // Function was implicitly deleted because the explicit exception
             // specification is incompatible with that of the implicit declaration.
}

Para corrigir esse código, remova o __declspec(nothrow) da função padronizada ou o = default e forneça uma definição para a função juntamente com qualquer manipulação de exceção necessária:

struct A {
    A& operator=(const A& other) {
        // ...
    }
};

struct B : public A {
    B& operator=(const B& other) = default;
};

int main()
{
    B b1, b2;
    b2 = b1;
}

Especializações parciais e noexcept

Com noexcept no sistema de tipos, as especializações parciais para correspondência de tipos "chamáveis" específicos poderá não compilar ou falhar ao escolher o modelo primário devido a uma especialização parcial ausente para funções de ponteiros para noexcept.

Nesses casos, pode ser necessário acrescentar outras especializações parciais adicionais para tratar os ponteiros de função noexcept e ponteiros noexcept para funções de membro. Essas sobrecargas só são válidas somente no modo /std:c++17 ou posterior. Se for necessário manter a compatibilidade com versões anteriores de C++14 e você estiver escrevendo código que outras pessoas consumirão, será necessário proteger essas novas sobrecargas dentro de diretivas #ifdef. Se você estiver trabalhando em um módulo independente, em vez de usar proteções #ifdef, será possível compilar apenas com a opção /Zc:noexceptTypes-.

O código a seguir é compilado em /std:c++14, mas falha em /std:c++17 com o erro C2027:

template <typename T> struct A;

template <>
struct A<void(*)()>
{
    static const bool value = true;
};

template <typename T>
bool g(T t)
{
    return A<T>::value;
}

void f() noexcept {}

int main()
{
    return g(&f) ? 0 : 1; // C2027: use of undefined type 'A<T>'
}

O código a seguir tem êxito no /std:c++17 porque o compilador escolhe a nova especialização parcial A<void (*)() noexcept>:

template <typename T> struct A;

template <>
struct A<void(*)()>
{
    static const bool value = true;
};

template <>
struct A<void(*)() noexcept>
{
    static const bool value = true;
};

template <typename T>
bool g(T t)
{
    return A<T>::value;
}

void f() noexcept {}

int main()
{
    return g(&f) ? 0 : 1; // OK
}

Aprimoramentos de conformidade na versão 15.6

Princípios básicos da biblioteca C++17 V1

O P0220R1 incorpora a especificação técnica dos princípios básicos da biblioteca para o C++17 no padrão. Aborda atualizações em <experimental/tuple>, <experimental/optional>, <experimental/functional>, <experimental/any>, <experimental/string_view>, <experimental/memory>, <experimental/memory_resource> e <experimental/algorithm>.

C++17: como aprimorar a dedução de argumento do modelo de classe para a biblioteca padrão

O P0739R0 move o adopt_lock_t para a frente da lista de parâmetros para scoped_lock a fim de habilitar o uso consistente de scoped_lock. Permite que o construtor std::variant participe na resolução de sobrecarga em mais casos para habilitar a atribuição de cópia.

Aprimoramentos de conformidade na versão 15.7

C++17 reescrevendo construtores de herança

P0136R1 especifica que uma declaração using que nomeia um construtor, agora torna os construtores de classe base correspondentes visíveis para inicializações da classe derivada, em vez de declarar mais construtores de classe derivada. Essa reformulação é uma mudança a partir do C++14. No Visual Studio 2017 versão 15.7 e posteriores, no modo /std:c++17 e posteriores, o código que é válido no C++14 e usa os construtores de herança pode não ser válido ou pode ter semântica diferente.

O exemplo a seguir mostra o comportamento do C++14:

struct A {
    template<typename T>
    A(T, typename T::type = 0);
    A(int);
};

struct B : A {
    using A::A;
    B(int n) = delete; // Error C2280
};

B b(42L); // Calls B<long>(long), which calls A(int)
          //  due to substitution failure in A<long>(long).

O seguinte exemplo mostra o comportamento do /std:c++17 no Visual Studio 15.7:

struct A {
    template<typename T>
    A(T, typename T::type = 0);
    A(int);
};

struct B : A {
    using A::A;
    B(int n)
    {
        //do something
    }
};

B b(42L); // now calls B(int)

Para saber mais, veja Construtores.

C++17: inicialização de agregação estendida

P0017R1

Se o construtor de uma classe base não é não público, mas é acessível a uma classe derivada, então, no modo /std:c++17 e posteriores do Visual Studio 2017 versão 15.7, não é mais possível usar chaves vazias para inicializar um objeto do tipo derivado. O seguinte exemplo mostra o comportamento de conformidade do C++14:

struct Derived;
struct Base {
    friend struct Derived;
private:
    Base() {}
};

struct Derived : Base {};
Derived d1; // OK. No aggregate init involved.
Derived d2 {}; // OK in C++14: Calls Derived::Derived()
               // which can call Base ctor.

No C++ 17, Derived agora é considerado um tipo de agregação. Isso significa que a inicialização de Base por meio do construtor padrão privado acontece diretamente como parte da regra de inicialização de agregação estendida. Anteriormente, o construtor privado Base era chamado por meio do construtor Derived, e isso era bem-sucedido devido à declaração friend. O seguinte exemplo mostra o comportamento do C++17 no Visual Studio versão 15.7, no modo /std:c++17:

struct Derived;
struct Base {
    friend struct Derived;
private:
    Base() {}
};
struct Derived : Base {
    Derived() {} // add user-defined constructor
                 // to call with {} initialization
};
Derived d1; // OK. No aggregate init involved.
Derived d2 {}; // error C2248: 'Base::Base': cannot access
               // private member declared in class 'Base'

C++17: declarando parâmetros de modelo de não tipo com auto

P0127R2

No modo /std:c++17, o compilador agora pode deduzir o tipo de um argumento de modelo de não tipo que é declarado com auto:

template <auto x> constexpr auto constant = x;

auto v1 = constant<5>;      // v1 == 5, decltype(v1) is int
auto v2 = constant<true>;   // v2 == true, decltype(v2) is bool
auto v3 = constant<'a'>;    // v3 == 'a', decltype(v3) is char

Um efeito desse novo recurso é que um código válido do C++14 pode não ser válido ou pode ter semântica diferente. Por exemplo, algumas sobrecargas que anteriormente eram inválidas, agora são válidas. O exemplo a seguir mostra um código do C++14 que é compilado porque a chamada a example(p) está associada a example(void*);. No Visual Studio 2017 versão 15.7, no modo /std:c++17, o modelo da função example é a melhor correspondência.

template <int N> struct A;
template <typename T, T N> int example(A<N>*) = delete;

void example(void *);

void sample(A<0> *p)
{
    example(p); // OK in C++14
}

O seguinte exemplo mostra um código do C++17 no Visual Studio 15.7, no modo /std:c++17:

template <int N> struct A;
template <typename T, T N> int example(A<N>*);

void example(void *);

void sample(A<0> *p)
{
    example(p); // C2280: 'int example<int,0>(A<0>*)': attempting to reference a deleted function
}

C++17: conversões elementares de cadeia de caracteres (parcial)

P0067R5 funções de nível baixo, independentes de localidade para conversões entre cadeias de caracteres e inteiros e entre cadeias de caracteres e números de ponto flutuante.

C++20: evitando decaimento desnecessário (parcial)

P0777R1 Adiciona diferenciação entre o conceito de "decaimento" e o da simples remoção de const ou de qualificadores de referência. A característica de novo tipo de remove_reference_t substitui decay_t em alguns contextos. O suporte a remove_cvref_t é implementado no Visual Studio 2019.

C++17: algoritmos paralelos

P0024R2 A TS de paralelismo será incorporada no padrão, com pequenas modificações.

C++17: hypot(x, y, z)

P0030R1 Adiciona três novas sobrecargas para std::hypot, para os tipos float, double e long double, cada um deles com três parâmetros de entrada.

C++17: <filesystem>

P0218R1 Adota a TS do sistema de arquivos no padrão, com algumas modificações de frase.

C++17: funções matemáticas especiais

P0226R1 Adota especificações técnicas anteriores para as funções matemáticas especiais no cabeçalho <cmath> padrão.

C++17: guias de dedução da biblioteca padrão

P0433R2 Atualizações ao STL para aproveitar a adoção do P0091R3 pelo C++17, que adiciona suporte à dedução de argumento de modelo de classe.

C++17: reparação de conversões elementares de cadeia de caracteres

P0682R1 Mover as novas funções de conversão de cadeia de caracteres elementares do P0067R5 para um novo cabeçalho <charconv> e fazer outros aprimoramentos, incluindo alterações no tratamento de erro para usar std::errc em vez de std::error_code.

C++17: constexpr para char_traits (parcial)

P0426R1 Alterações nas funções de membro std::traits_type, length, compare e find para tornar std::string_view utilizável em expressões de constante. (No Visual Studio 2017 versão 15.6, com suporte apenas para Clang/LLVM. Na versão 15.7, o suporte está quase concluído para ClXX também).

C++17: argumento padrão no modelo de classe primária

Essa alteração de comportamento é uma pré-condição para P0091R3 - Template argument deduction for class templates.

Anteriormente, o compilador ignorava o argumento padrão no modelo de classe primária:

template<typename T>
struct S {
    void f(int = 0);
};

template<typename T>
void S<T>::f(int = 0) {} // Re-definition necessary

No modo /std:c++17 do Visual Studio 2017 versão 15.7, o argumento padrão não é ignorado:

template<typename T>
struct S {
    void f(int = 0);
};

template<typename T>
void S<T>::f(int) {} // Default argument is used

Resolução de nome dependente

Essa alteração de comportamento é uma pré-condição para P0091R3 - Template argument deduction for class templates.

No exemplo a seguir, o compilador do Visual Studio 15.6 e anterior resolve D::type como B<T>::type no modelo de classe primária.

template<typename T>
struct B {
    using type = T;
};

template<typename T>
struct D : B<T*> {
    using type = B<T*>::type;
};

O Visual Studio 2017 versão 15.7, no modo /std:c++17, requer a palavra-chave typename na instrução using em D. Sem typename, o compilador gera um aviso C4346: 'B<T*>::type': dependent name is not a type e o erro C2061: syntax error: identifier 'type':

template<typename T>
struct B {
    using type = T;
};

template<typename T>
struct D : B<T*> {
    using type = typename B<T*>::type;
};

C++17: Atributo [[nodiscard]] – aumento do nível de aviso

No Visual Studio 2017 versão 15.7 no modo /std:c++17, o nível de aviso do C4834 é aumentado de W3 para W1. Você pode desabilitar o aviso com uma conversão para void, ou passando /wd:4834 para o compilador.

[[nodiscard]] int f() { return 0; }

int main() {
    f(); // warning C4834: discarding return value
         // of function with 'nodiscard'
}

Lista de inicialização de classe base do construtor de modelo Variadic

Em edições anteriores do Visual Studio, uma lista de inicialização de classe base do construtor de modelo variadic que não continha argumentos de modelos foi erroneamente permitida sem erro. No Visual Studio 2017 versão 15.7 é gerado um erro do compilador.

O exemplo de código a seguir no Visual Studio 2017 versão 15.7 gera erro C2614:

template<typename T>
struct B {};

template<typename T>
struct D : B<T>
{

    template<typename ...C>
    D() : B() {} // C2614: D<int>: illegal member initialization: 'B' is not a base or member
};

D<int> d;

Para corrigir o erro, altere a expressão B() para B<T>().

Inicialização de agregação de constexpr

As versões anteriores do compilador do C++ manipulavam incorretamente a inicialização de agregação constexpr. O compilador aceitava código inválido no qual a agregação init-list tinha muitos elementos e produzia um código de objeto inválido para ela. O código a seguir é um exemplo desse código:

#include <array>
struct X {
    unsigned short a;
    unsigned char b;
};

int main() {
    constexpr std::array<X, 2> xs = { // C2078: too many initializers
        { 1, 2 },
        { 3, 4 }
    };
    return 0;
}

No Visual Studio 2017 versão 15.7 atualização 3 e versões posteriores, o exemplo anterior agora gera o erro C2078. O exemplo a seguir mostra como corrigir o código. Ao inicializar um std::array com chaves init-lists aninhadas, forneça para matriz interna uma lista entre chaves própria:

#include <array>
struct X {
    unsigned short a;
    unsigned char b;
};

int main() {
    constexpr std::array<X, 2> xs = {{ // note double braces
        { 1, 2 },
        { 3, 4 }
    }}; // note double braces
    return 0;
}

Aprimoramentos de conformidade na versão 15.8

typename em identificadores não qualificados

No modo /permissive-, palavras-chave typename falsas em identificadores não qualificados nas definições de modelo de alias não são mais aceitas pelo compilador. Agora, o seguinte código produz o C7511:

template <typename T>
using  X = typename T; // C7511: 'T': 'typename' keyword must be 
                       // followed by a qualified name

Para corrigir o erro, altere a segunda linha para using X = T;.

__declspec() no lado direito das definições de modelo de alias

__declspec não é mais permitido no lado direito de uma definição de modelo de alias. Anteriormente, o compilador aceitava, mas ignorava esse código. Isso nunca resultaria em um aviso de reprovação quando o alias fosse usado.

O atributo [[deprecated]] do C++ padrão pode ser usado nesse caso e é respeitado no Visual Studio 2017 versão 15.6. Agora, o código a seguir produz o C2760:

template <typename T>
using X = __declspec(deprecated("msg")) T; // C2760: syntax error:
                                           // unexpected token '__declspec',
                                           // expected 'type specifier'`

Para corrigir o erro, altere o código da seguinte forma (com o atributo colocado antes de '=' da definição do alias):

template <typename T>
using  X [[deprecated("msg")]] = T;

Diagnóstico de pesquisa de nome em duas fases

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, o compilador C++ da Microsoft deixaria um nome não encontrado como não pesquisado até o momento da instanciação. Agora, ele exige que os nomes não dependentes estejam vinculados no corpo do modelo.

Uma forma em que isso se manifesta é com a pesquisa em classes base dependentes. Anteriormente, o compilador permitia o uso de nomes definidos nas classes base dependentes. Isso ocorria porque eles seriam pesquisados durante o tempo de instanciação, quando todos os tipos eram resolvidos. Agora, esse código é tratado como um erro. Nesses casos, você pode forçar a pesquisa da variável no tempo de instanciação qualificando-a com o tipo de classe base ou, de outro modo, tornando-a dependente, por exemplo, adicionando um ponteiro this->.

Agora, no modo /permissive-, o seguinte código gera o C3861:

template <class T>
struct Base {
    int base_value = 42;
};

template <class T>
struct S : Base<T> {
    int f() {
        return base_value; // C3861: 'base_value': identifier not found
    }
};

Para corrigir o erro, altere a instrução return para return this->base_value;.

Observação

Nas versões da biblioteca Boost.Python anteriores à 1.70, houve uma solução alternativa específica do MSVC para uma declaração de encaminhamento de modelo no unwind_type.hpp. No modo /permissive-, a partir do Visual Studio 2017 versão 15.8 (_MSC_VER==1915), o compilador do MSVC faz a pesquisa do ADL (nome dependente de argumento) corretamente. Agora ele é consistente com outros compiladores, tornando essa proteção alternativa desnecessária. Para evitar o erro C3861: 'unwind_type': identifier not found, atualize sua biblioteca Boost.Python.

Declarações e definições de encaminhamento no namespace std

O padrão C++ não permite que um usuário adicione declarações ou definições de encaminhamento no namespace std. A adição de declarações ou de definições ao namespace std ou a um namespace no namespace std agora resulta em um comportamento indefinido.

Em algum momento no futuro, a Microsoft mudará o local em que alguns tipos padrão são definidos. Essa mudança interromperá o código existente que adiciona as declarações de encaminhamento ao namespace std. Um novo aviso, o C4643, ajuda a identificar esses problemas de origem. O aviso é habilitado no modo /default e fica desativado por padrão. Isso afetará programas compilados com /Wall ou /WX.

O código a seguir agora gera C4643:

namespace std {
    template<typename T> class vector;  // C4643: Forward declaring 'vector'
                                        // in namespace std is not permitted
                                        // by the C++ Standard`
}

Para corrigir o erro, use uma diretiva #include em vez de uma declaração de encaminhamento:

#include <vector>

Construtores que delegam a si mesmos

O padrão C++ sugere que um compilador deve emitir um diagnóstico quando um construtor de delegação delega a si mesmo. Agora, o compilador do Microsoft C++ nos modos /std:c++17 e /std:c++latest gera o C7535.

Sem esse erro, o programa a seguir será compilado, mas gerará um loop infinito:

class X {
public:
    X(int, int);
    X(int v) : X(v){} // C7535: 'X::X': delegating constructor calls itself
};

Para evitar o loop infinito, delegue para um construtor diferente:

class X {
public:

    X(int, int);
    X(int v) : X(v, 0) {}
};

offsetof com expressões de constante

offsetof tem sido tradicionalmente implementado usando uma macro que requer um reinterpret_cast. Esse uso não é válido em contextos que requerem uma expressão de constante, mas usualmente o compilador C++ da Microsoft tem permitido isso. A macro offsetof, que é fornecida como parte da biblioteca padrão, usa corretamente um compilador intrínseco (__builtin_offsetof), mas muitas pessoas têm usado o truque da macro para definir o próprio offsetof.

No Visual Studio 2017 versão 15.8, o compilador restringe as áreas em que esses operadores reinterpret_cast podem aparecer no modo padrão para ajudar o código a ficar em conformidade com o comportamento do C++ padrão. Em /permissive-, as restrições são ainda mais rígidas. O uso do resultado de um offsetof em locais que requerem expressões de constante pode resultar em um código que emite o aviso C4644 ou C2975.

O seguinte código gera o C4644 nos modos padrão e /std:c++17 padrão e o C2975 no modo /permissive-:

struct Data {
    int x;
};

// Common pattern of user-defined offsetof
#define MY_OFFSET(T, m) (unsigned long long)(&(((T*)nullptr)->m))

int main()

{
    switch (0) {
    case MY_OFFSET(Data, x): return 0; // C4644: usage of the
        // macro-based offsetof pattern in constant expressions
        // is non-standard; use offsetof defined in the C++
        // standard library instead
        // OR
        // C2975: invalid template argument, expected
        // compile-time constant expression

    default: return 1;
    }
}

Para corrigir o erro, use offsetof conforme a definição por meio de <cstddef>:

#include <cstddef>

struct Data {
    int x;
};

int main()
{
    switch (0) {
    case offsetof(Data, x): return 0;
    default: return 1;
    }
}

qualificadores de cv em classes base sujeitos a expansão de pacote

As versões anteriores do compilador C++ da Microsoft não detectavam que uma classe base tinha qualificadores de cv se ele também estivesse sujeito à expansão do pacote.

No Visual Studio 2017 versão 15.8, no modo /permissive-, o seguinte código gera o C3770:

template<typename... T>
class X : public T... { };

class S { };

int main()
{
    X<const S> x; // C3770: 'const S': is not a valid base class
}

Palavra-chave template e especificadores de nome aninhados

No modo /permissive-, o compilador agora requer que a palavra-chave template preceda um nome de modelo quando ele vier depois de um especificador de nome aninhado dependente.

Agora, no modo /permissive-, o seguinte código gera o C7510:

template<typename T> struct Base
{
    template<class U> void example() {}
};

template<typename T>
struct X : Base<T>
{
    void example()
    {
        Base<T>::example<int>(); // C7510: 'example': use of dependent
            // template name must be prefixed with 'template'
            // note: see reference to class template instantiation
            // 'X<T>' being compiled
    }
};

Para corrigir o erro, adicione a palavra-chave template à instrução Base<T>::example<int>();, conforme é mostrado no exemplo a seguir:

template<typename T> struct Base
{
    template<class U> void example() {}
};

template<typename T>
struct X : Base<T>
{
    void example()
    {
        // Add template keyword here:
        Base<T>::template example<int>();
    }
};

Aprimoramentos de conformidade na versão 15.9

Ordem de avaliação da esquerda para a direita para os operadores ->*, [], >> e <<

A partir do C++17, os operandos dos operadores ->*, [], >> e << devem ser avaliados na ordem da esquerda para a direita. Há dois casos em que o compilador não conseguirá garantir esta ordem:

  • quando uma das expressões do operando é um objeto passado por valor ou que contém um objeto passado por valor ou

  • quando é compilado usando /clr e um dos operandos é um campo de um elemento de matriz ou objeto.

O compilador emite um aviso C4866 quando não é capaz de garantir a avaliação da esquerda para a direita. O compilador vai gerar esse aviso apenas se /std:c++17 ou posterior for especificado, já que o requisito de ordem da esquerda para direita desses operadores foi introduzido no C++17.

Para tratar desse aviso, considere primeiro se a avaliação da esquerda para a direita dos operandos é necessária. Por exemplo, ela pode ser necessária quando a avaliação dos operandos puder produzir efeitos colaterais dependentes da ordem. A ordem em que os operandos são avaliados não tem efeitos observáveis em muitos casos. Se a ordem da avaliação precisar ser da esquerda para a direita, considere se não é possível passar os operandos por referência const. Essa alteração elimina o aviso no seguinte exemplo de código:

// C4866.cpp
// compile with: /w14866 /std:c++17

class HasCopyConstructor
{
public:
    int x;

    HasCopyConstructor(int x) : x(x) {}
    HasCopyConstructor(const HasCopyConstructor& h) : x(h.x) { }
};

int operator>>(HasCopyConstructor a, HasCopyConstructor b) { return a.x >> b.x; }

// This version of operator>> does not trigger the warning:
// int operator>>(const HasCopyConstructor& a, const HasCopyConstructor& b) { return a.x >> b.x; }

int main()
{
    HasCopyConstructor a{ 1 };
    HasCopyConstructor b{ 2 };

    a>>b;        // C4866 for call to operator>>
};

Identificadores em modelos de alias de membro

Um identificador usado em uma definição de modelo de alias de membro precisa ser declarado antes de ser usado.

Nas versões anteriores do compilador, o código a seguir era permitido. No Visual Studio 2017 versão 15.9, no modo /permissive-, o compilador gera o C3861:

template <typename... Ts>
struct A
{
  public:
    template <typename U>
    using from_template_t = decltype(from_template(A<U>{})); // C3861:
        // 'from_template': identifier not found

  private:
    template <template <typename...> typename Type, typename... Args>
    static constexpr A<Args...> from_template(A<Type<Args...>>);
};

A<>::from_template_t<A<int>> a;

Para corrigir o erro, declare from_template antes de from_template_t.

Alterações nos módulos

No Visual Studio 2017, versão 15.9, o compilador gera o C5050 sempre que as opções de linha de comando dos módulos não são consistentes entre os lados da criação do módulo e do consumo do módulo. No exemplo a seguir, há dois problemas:

  • No lado do consumo (main.cpp), a opção /EHsc não está especificada.

  • A versão do C++ é /std:c++17 no lado da criação e /std:c++14 no lado do consumo.

cl /EHsc /std:c++17 m.ixx /experimental:module
cl /experimental:module /module:reference m.ifc main.cpp /std:c++14

O compilador gera o C5050 para ambos os casos:

warning C5050: Possible incompatible environment while
importing module 'm': mismatched C++ versions.
Current "201402" module version "201703".

O compilador também gera o C7536 sempre que o arquivo .ifc é adulterado. O cabeçalho da interface do módulo contém um hash SHA2 do conteúdo abaixo dele. Na importação, o arquivo .ifc passa por hash e, em seguida, é verificado em relação ao hash fornecido no cabeçalho. Se eles não corresponderem, será gerado o erro C7536:

error C7536: ifc failed integrity checks.
Expected SHA2: '66d5c8154df0c71d4cab7665bab4a125c7ce5cb9a401a4d8b461b706ddd771c6'

Ordenação parcial envolvendo aliases e contextos não deduzidos

As implementações divergem nas regras de ordenação parciais que envolvem aliases em contextos não deduzidos. No exemplo a seguir, a GCC e o compilador do Microsoft C++ (no modo /permissive-) geram um erro, embora o Clang aceite o código.

#include <utility>
using size_t = std::size_t;

template <typename T>
struct A {};
template <size_t, size_t>
struct AlignedBuffer {};
template <size_t len>
using AlignedStorage = AlignedBuffer<len, 4>;

template <class T, class Alloc>
int f(Alloc &alloc, const AlignedStorage<T::size> &buffer)
{
    return 1;
}

template <class T, class Alloc>
int f(A<Alloc> &alloc, const AlignedStorage<T::size> &buffer)
{
    return 2;
}

struct Alloc
{
    static constexpr size_t size = 10;
};

int main()
{
    A<void> a;
    AlignedStorage<Alloc::size> buf;
    if (f<Alloc>(a, buf) != 2)
    {
        return 1;
    }

    return 0;
}

O exemplo anterior gera o C2668:

partial_alias.cpp(32): error C2668: 'f': ambiguous call to overloaded function
partial_alias.cpp(18): note: could be 'int f<Alloc,void>(A<void> &,const AlignedBuffer<10,4> &)'
partial_alias.cpp(12): note: or       'int f<Alloc,A<void>>(Alloc &,const AlignedBuffer<10,4> &)'
        with
        [
            Alloc=A<void>
        ]
partial_alias.cpp(32): note: while trying to match the argument list '(A<void>, AlignedBuffer<10,4>)'

A divergência de implementação se deve a uma regressão na expressão do padrão C++. A resolução para edição de núcleo de 2235 removeu algum texto que permitiria que essas sobrecargas fossem ordenadas. O padrão C++ atual não fornece um mecanismo para ordenar parcialmente essas funções, portanto, elas são consideradas ambíguas.

Como solução alternativa, é recomendável não depender de ordenação parcial para resolver esse problema. Em vez disso, use SFINAE para remover sobrecargas específicas. No exemplo a seguir, usamos uma classe auxiliar IsA para remover a primeira sobrecarga quando Alloc é uma especialização de A:

#include <utility>
using size_t = std::size_t;

template <typename T>
struct A {};
template <size_t, size_t>
struct AlignedBuffer {};
template <size_t len>
using AlignedStorage = AlignedBuffer<len, 4>;

template <typename T> struct IsA : std::false_type {};
template <typename T> struct IsA<A<T>> : std::true_type {};

template <class T, class Alloc, typename = std::enable_if_t<!IsA<Alloc>::value>>
int f(Alloc &alloc, const AlignedStorage<T::size> &buffer)
{
    return 1;
}

template <class T, class Alloc>
int f(A<Alloc> &alloc, const AlignedStorage<T::size> &buffer)
{
    return 2;
}

struct Alloc
{
    static constexpr size_t size = 10;
};

int main()
{
    A<void> a;
    AlignedStorage<Alloc::size> buf;
    if (f<Alloc>(a, buf) != 2)
    {
        return 1;
    }

    return 0;
}

Expressões ilegais e tipos não literais em definições de função com modelo

Expressões ilegais e tipos não literais agora são diagnosticados corretamente nas definições de funções com modelo especializadas explicitamente. Anteriormente, esses erros não eram emitidos para a definição da função. No entanto, o tipo não literal ou a expressão ilegal ainda teria sido diagnosticado se avaliado como parte de uma expressão de constante.

Em versões anteriores do Visual Studio, o seguinte código é compilado sem aviso:

void g();

template<typename T>
struct S
{
    constexpr void f();
};

template<>
constexpr void S<int>::f()
{
    g(); // C3615 in 15.9
}

No Visual Studio 2017 versão 15.9, o código gera o erro C3615:

error C3615: constexpr function 'S<int>::f' cannot result in a constant expression.
note: failure was caused by call of undefined function or one not declared 'constexpr'
note: see usage of 'g'.

Para evitar o erro, remova o qualificador constexpr da instanciação explícita da função f().

Confira também

Conformidade com a linguagem do Microsoft C/C++