Améliorations de la conformité, changements de comportement et correctifs de bogues de C++ dans Visual Studio 2019

Microsoft C/C++ dans Visual Studio (MSVC) apporte des améliorations de la conformité et des correctifs de bogues dans chaque version. Cet article répertorie les améliorations apportées par version majeure, puis par version. Pour aller directement aux modifications apportées à une version spécifique, utilisez la liste ci-dessous dans cet article.

Ce document répertorie les modifications apportées à Visual Studio 2019. Pour obtenir un guide des modifications apportées à Visual Studio 2022, consultez Améliorations de la conformité de C++ dans Visual Studio 2022. Pour connaître les modifications apportées à Visual Studio 2017, consultez Améliorations de la conformité de C++ dans Visual Studio 2017. Pour obtenir la liste complète des améliorations de la conformité précédentes, consultez Nouveautés de Visual C++ entre 2003 et 2015.

Améliorations de la conformité dans Visual Studio 2019 RTW (version 16.0)

Visual Studio 2019 RTW contient les améliorations, les correctifs de bogues et les changements de comportement relatifs à la conformité suivants dans le compilateur Microsoft C++.

Notes

Les fonctionnalités de C++20 étaient disponibles uniquement en mode /std:c++latest dans Visual Studio 2019 jusqu’à ce que l’implémentation de C++20 soit considérée comme terminée. Visual Studio 2019 version 16.11 introduit le mode compilateur /std:c++20. Dans cet article, les fonctionnalités qui nécessitaient à l’origine le mode /std:c++latest fonctionnent désormais en mode /std:c++20 ou ultérieur dans les dernières versions de Visual Studio. Nous avons mis à jour la documentation pour mentionner /std:c++20, même si cette option n’était pas disponible lorsque les fonctionnalités ont été publiées pour la première fois.

Amélioration de la prise en charge des modules pour les modèles et la détection d’erreur

Les modules sont désormais officiellement dans la norme C++20. Une meilleure prise en charge a été ajoutée dans Visual Studio 2017 version 15.9. Pour plus d’informations, consultez Better template support and error detection in C++ Modules with MSVC 2017 version 15.9.

Changement de spécification du type d’agrégat

La spécification d’un type d’agrégat a changé dans C++20 (consultez Prohibit aggregates with user-declared constructors). Dans Visual Studio 2019, sous /std:c++latest (ou /std:c++20 dans Visual Studio 2019 versions 16.11 et ultérieures), une classe avec un constructeur déclaré par l’utilisateur (par exemple un constructeur déclaré = default ou = delete) n’est pas un agrégat. Avant, seuls les constructeurs fournis par l’utilisateur empêchaient une classe d’être un agrégat. Ce changement ajoute des restrictions quant à la façon dont ces types peuvent être initialisés.

Le code suivant se compile sans erreur dans Visual Studio 2017, mais génère des erreurs C2280 et C2440 dans Visual Studio 2019 sous /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

Prise en charge partielle de operator <=>

P0515R3C++20 introduit l’opérateur de comparaison trilatérale <=>, également appelé « opérateur vaisseau spatial ». Visual Studio 2019 version 16.0 en mode /std:c++latest introduit la prise en charge partielle de l’opérateur en générant des erreurs pour la syntaxe qui est maintenant interdite. Par exemple, le code suivant se compile sans erreur dans Visual Studio 2017, mais génère plusieurs erreurs dans Visual Studio 2019 sous /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.
}

Pour éviter les erreurs, insérez un espace dans la ligne incriminée avant le crochet angulaire fermant : U<&S::operator<= > u;.

Références aux types avec des qualificateurs cv non correspondants

Notes

Cette modification affecte uniquement Visual Studio 2019 versions 16.0 à 16.8. Elle a été rétablie à partir de Visual Studio 2019 version 16.9

Avant, MSVC autorisait la liaison directe d’une référence d’un type avec des qualificateurs cv non correspondants sous le niveau supérieur. Cette liaison pouvait permettre la modification des données const supposément référencées par la référence.

Le compilateur pour Visual Studio 2019 versions 16.0 à 16.8 crée plutôt un élément temporaire, tel que requise par la norme en vigueur. Plus tard, la norme a changé rétroactivement le comportement précédent correct de Visual Studio 2017 et versions antérieures, et le comportement incorrect de Visual Studio 2019 versions 16.0 à 16.8. Par conséquent, cette modification a été rétablie à partir de Visual Studio 2019 version 16.9.

Pour une modification associée, consultez Types similaires et liaison de référence.

Par exemple, dans Visual Studio 2017, le code suivant se compile sans avertissement. Dans Visual Studio 2019 versions 16.0 à 16.8, le compilateur lève l’avertissement C4172. Depuis Visual Studio 2019 version 16.9, le code se compile une fois encore sans avertissements :

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 d’une fonction surchargée

L’argument pour reinterpret_cast ne fait pas partie des contextes dans lesquels l’adresse d’une fonction surchargée est autorisée. Le code suivant se compile sans erreur dans Visual Studio 2017, alors que dans Visual Studio 2019 il lève l’erreur 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'
}

Pour éviter cette erreur, utilisez un cast autorisé pour ce scénario :

int f(int);
int f(float);
using fp = int(*)(int);

int main()
{
    fp r = static_cast<fp>(&f); // or just &f;
}

Fermetures lambda

Dans C++14, les types de fermeture d’expression lambda ne sont pas littéraux. La première conséquence de cette règle est qu’une expression lambda ne doit pas être affectée à une variable constexpr. Le code suivant se compile sans erreur dans Visual Studio 2017, alors que dans Visual Studio 2019 il lève l’erreur C2127 :

int main()
{
    constexpr auto l = [] {}; // C2127 'l': illegal initialization of 'constexpr' entity with a non-constant expression
}

Pour éviter cette erreur, supprimez le qualificateur constexpr ou changez le mode de conformité en /std:c++17 ou version ultérieure.

Codes d’échec std::create_directory

P1164 implémenté depuis C++20 sans condition. Cela change std::create_directory pour vérifier si la cible était déjà un répertoire en échec. Avant, toutes les erreurs de type ERROR_ALREADY_EXISTS étaient converties en codes success-but-directory-not-created.

operator<<(std::ostream, nullptr_t)

Conformément à LWG 2221, ajout de operator<<(std::ostream, nullptr_t) pour écrire nullptr dans les flux.

Autres algorithmes parallèles

Nouvelles versions parallèles de is_sorted, is_sorted_until, is_partitioned, set_difference, set_intersection, is_heap et is_heap_until.

Correctifs dans l’initialisation atomique

P0883 "Fixing atomic initialization" change std::atomic pour l’initialiser avec la valeur du T contenu plutôt que de l’initialiser avec la valeur par défaut. Le correctif est activé lorsque vous utilisez Clang/LLVM avec la bibliothèque standard de Microsoft. Il est actuellement désactivé pour le compilateur Microsoft C++ comme solution de contournement pour un bogue dans le traitement de constexpr.

remove_cvref et remove_cvref_t

Caractéristiques de type remove_cvref et remove_cvref_t implémentées selon le P0550. Cela supprime reference-ness et cv-qualification d’un type sans dégrader les fonctions et les tableaux en pointeurs (contrairement à std::decay et std::decay_t).

Macros Feature-test

P0941R2 - macros de test de fonctionnalité est terminé, avec la prise en charge de __has_cpp_attribute. Les macros de test de fonctionnalité sont prises en charge dans tous les modes standard.

Interdire les agrégats avec les constructeurs déclarés par l’utilisateur

C++20 P1008R1 - prohibiting aggregates with user-declared constructors est terminé.

reinterpret_cast dans une fonction constexpr

Un reinterpret_cast est non conforme dans une fonction constexpr. Précédemment ,le compilateur Microsoft C++ rejetait une reinterpret_cast uniquement s’il était utilisé dans un contexte constexpr. Dans Visual Studio 2019, dans tous les modes de normes du langage, le compilateur diagnostique correctement un reinterpret_cast dans la définition d’une fonction constexpr. Le code suivant génère désormais l’erreur C3615 :

long long i = 0;
constexpr void f() {
    int* a = reinterpret_cast<int*>(i); // C3615: constexpr function 'f' cannot result in a constant expression
}

Pour éviter l’erreur, supprimez le modificateur constexpr de la déclaration de fonction.

Diagnostics corrects pour le constructeur de plage basic_string

Dans Visual Studio 2019, le constructeur de plage basic_string ne supprime plus les diagnostics du compilateur avec static_cast. Le code suivant se compile sans avertissement dans Visual Studio 2017, malgré la perte de données possible de wchar_t à char lors de l’initialisation de 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.

Visual Studio 2019 lève correctement l’avertissement C4244. Pour éviter cet avertissement, vous pouvez initialiser la std::string comme illustré dans cet exemple :

std::wstring ws = L"Hello world";
std::string out;
for (wchar_t ch : ws)
{
    out.push_back(static_cast<char>(ch));
}

Les appels incorrects à += et -= sous /clr ou /ZW sont maintenant correctement détectés

Un bogue a été introduit dans Visual Studio 2017 qui faisait que le compilateur ignorait les erreurs en mode silencieux et ne générait aucun code pour les appels non valides à += et -= sous /clr ou /ZW. Le code suivant se compile sans erreur dans Visual Studio 2017, alors que dans Visual Studio 2019 il déclenche correctement l’erreur 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.
}

Pour éviter l’erreur dans cet exemple, utilisez l’opérateur += avec la méthode ToString() : s += E::e.ToString();.

Initialiseurs pour les membres de données statiques inline

Les accès à des membres non valides dans les initialiseurs inline et static constexpr sont maintenant correctement détectés. L’exemple suivant se compile sans erreur dans Visual Studio 2017, tandis que, dans Visual Studio 2019 sous /std:c++17 ou version ultérieure, il lève l’erreur 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'.
};

Pour éviter cette erreur, déclarez le membre X::c comme protégé :

struct X
{
    protected:
        static inline const int c = 1000;
};

C4800 réintégré

MSVC produisait un avertissement C4800 sur les performances au sujet de la conversion implicite en bool. Il était trop bruyant et ne pouvait pas être supprimé ; nous avons donc choisi de le supprimer dans Visual Studio 2017. Toutefois, pendant le cycle de vie de Visual Studio 2017, nous avons eu beaucoup de commentaires disant qu’il était utile pour résoudre de nombreux cas. Nous avons rajouté à Visual Studio 2019 un avertissement C4800 soigneusement adapté, ainsi qu’un C4165 explicatif. Ces deux avertissements sont faciles à supprimer, soit à l’aide d’un cast explicite, soit par une comparaison à 0 du type approprié. C4800 est un avertissement de niveau 4 désactivé par défaut, tandis que C4165 est un avertissement de niveau 3 désactivé par défaut. Les deux sont découvrables à l’aide de l’option de compilateur /Wall.

L’exemple suivant déclenche C4800 et C4165 sous /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?
}

Pour éviter les avertissements dans l’exemple précédent, vous pouvez écrire le code comme ceci :

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
}

La fonction membre de classe locale n’a pas de corps

Dans Visual Studio 2017, l’avertissement C4822 est levé uniquement lorsque l’option /w14822 du compilateur est définie explicitement. Il n’est pas affiché avec /Wall. Dans Visual Studio 2019, C4822 est un avertissement désactivé par défaut, ce qui le rend découvrable sous /Wall sans avoir à définir /w14822 explicitement.

void example()
{
    struct A
        {
            int boo(); // warning C4822: Local class member function doesn't have a body
        };
}

Corps de modèle de fonction contenant des instructions if constexpr

Dans Visual Studio 2019 sous /std:c++20 ou /std:c++latest, les corps de fonction de modèle qui ont des instructions if constexpr ont des vérifications liées à l’analyse supplémentaires activées. Par exemple, dans Visual Studio 2017, le code suivant génère l’erreur C7510 uniquement si l’option /permissive- est définie. Dans Visual Studio 2019, le même code génère des erreurs, même si l’option /permissive est définie :

// 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";
}

Pour éviter cette erreur, ajoutez le mot clé typename à la déclaration de a : typename T::Type a;.

Le code de l’assembleur inline n’est pas pris en charge dans une expression lambda

L’équipe Microsoft C++ a récemment été informée d’un problème de sécurité où l’utilisation de inline-assembler dans une expression lambda pouvait entraîner l’altération de ebp (l’enregistrement de l’adresse de retour) lors de l’exécution. Une personne malveillante pouvait éventuellement tirer parti de ce scénario. L’assembleur inline n’est pris en charge que sur x86, et l’interaction entre l’assembleur inline et le reste du compilateur est médiocre. Compte tenu de cela et de la nature du problème, la solution la plus sûre à ce problème était de interdire l’assembleur inline dans une expression lambda.

La seule utilisation de l’assembleur inline dans une expression lambda que nous avons rencontrée avait pour but de capturer l’adresse de retour. Dans ce scénario, vous pouvez capturer l’adresse de retour sur toutes les plateformes simplement à l’aide de _ReturnAddress() intrinsèque au compilateur.

Le code suivant génère l’erreur C7553 dans Visual Studio 2017 15.9 et versions ultérieures de 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;
}

Pour éviter cette erreur, déplacez le code d’assembly dans une fonction nommée comme indiqué dans l’exemple suivant :

#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());
}

Débogage d’itérateur et std::move_iterator

La fonctionnalité de débogage d’itérateur a été adaptée pour unwrapper correctement std::move_iterator. Par exemple, std::copy(std::move_iterator<std::vector<int>::iterator>, std::move_iterator<std::vector<int>::iterator>, int*) peut maintenant engager le chemin rapide memcpy.

Correctifs pour l’application du mot clé <xkeycheck.h>

L’application de la bibliothèque standard dans <xkeycheck.h> pour les macros remplaçant un mot clé a été corrigée. La bibliothèque émet désormais le mot clé de problème réel détecté plutôt qu’un message générique. Elle prend aussi en charge les mots clés C++20 et évite qu’IntelliSense indique que des mots clés aléatoires sont des macros.

Les types d’allocateurs ne sont plus déconseillés

std::allocator<void>, std::allocator::size_type et std::allocator::difference_type ne sont plus déconseillés.

Avertissement correct pour les conversions de chaîne restrictives

Suppression d’un static_cast parasite de std::string qui n’était pas appelé par la norme, et qui supprimait accidentellement les avertissements restrictifs C4244. Les tentatives d’appel de std::string::string(const wchar_t*, const wchar_t*) émettent désormais correctement un avertissement C4244 sur la restriction d’un wchar_t dans un char.

Divers correctifs pour la justesse de <filesystem>

  • Correction de l’échec de std::filesystem::last_write_time lors d’une tentative de changement de l’heure de dernière écriture d’un répertoire.
  • Désormais, le constructeur std::filesystem::directory_entry stocke un résultat en échec plutôt que de lever une exception quand un chemin cible qui n’existe pas est fourni.
  • La version à 2 paramètres std::filesystem::create_directory a été changée pour appeler la version à 1 paramètre, car la fonction CreateDirectoryExW sous-jacente utiliserait copy_symlink si existing_p était un symlink.
  • std::filesystem::directory_iterator n’échoue plus lorsqu’un symkink rompu est trouvé.
  • std::filesystem::space accepte désormais les chemins relatifs.
  • std::filesystem::path::lexically_relative n’est plus dérouté par les barres obliques de fin, signalé dans LWG 3096.
  • Le problème a été contourné avec CreateSymbolicLinkW qui rejette les chemins avec des barres obliques dans std::filesystem::create_symlink.
  • Contournement de la fonction delete du mode de suppression POSIX qui existait dans Windows 10 LTSB 1609, mais ne pouvait pas réellement supprimer des fichiers.
  • Les constructeurs de copie de std::boyer_moore_searcher et std::boyer_moore_horspool_searcher, et les opérateurs d’affectation de copie arrivent désormais à copier.

Algorithmes parallèles sur Windows 8 et ultérieur

La bibliothèque d’algorithmes parallèles utilise la vraie famille WaitOnAddress sur Windows 8 et ultérieur, au lieu d’utiliser tout le temps les versions fausses de Windows 7 et antérieur.

Espace blanc std::system_category::message()

std::system_category::message() supprime désormais l’espace de fin du message retourné.

Division par zéro de std::linear_congruential_engine

Certaines conditions qui faisaient que std::linear_congruential_engine déclenchait la division par 0 ont été corrigées.

Correctifs pour l’unwrapping d’itérateurs

Certaines machines de désencapsulation d’itérateur étaient d’abord exposées pour l’intégration de programmeur-utilisateur dans Visual Studio 2017 15.8. Cela a été décrit dans un article du blog de l’équipe C++, STL Features and Fixes in VS 2017 15.8. Cette machine ne désencapsule plus les itérateurs dérivés des itérateurs de bibliothèque standard. Par exemple, un utilisateur qui dérive de std::vector<int>::iterator et qui essaie de personnaliser le comportement obtient son comportement personnalisé lors de l’appel des algorithmes de la bibliothèque standard au lieu du comportement d’un pointeur.

La fonction de réserve de conteneur non ordonnée reserve maintenant pour N éléments, comme décrit dans LWG 2156.

Gestion de l’heure

  • Avant, certaines valeurs de temps passées à la bibliothèque de concurrence provoquaient un dépassement, par exemple, condition_variable::wait_for(seconds::max()). Ces dépassements, maintenant corrigés, changeaient le comportement selon un cycle apparemment aléatoire de 29 jours (quand les millisecondes uint32_t acceptées par les API Win32 sous-jacentes dépassaient).

  • L’en-tête <ctime> déclare désormais correctement timespec et timespec_get dans l’espace de noms std, ainsi que dans l’espace de noms global.

Divers correctifs pour les conteneurs

  • De nombreuses fonctions de conteneur internes à la bibliothèque Standard ont été rendues private pour une meilleure expérience IntelliSense. Des correctifs supplémentaires pour marquer les membres comme private sont attendus dans les prochaines versions de MSVC.

  • Nous avons résolu des problèmes de justesse de la sécurité des exceptions qui entraînaient la corruption de conteneurs basés sur un nœud, tels que list, map et unordered_map. Pendant une opération de réaffectation propagate_on_container_copy_assignment ou propagate_on_container_move_assignment, nous libérons le nœud sentinelle du conteneur avec l’ancien allocateur, procédons à l’affectation de POCCA/POCMA sur l’ancien allocateur, puis tentons d’obtenir le nœud sentinelle auprès du nouvel allocateur. Si cette allocation échouait, le conteneur était endommagé. Il ne pouvait même pas être détruit, car posséder un nœud sentinelle est un invariant d’une structure de données. Ce code a été corrigé pour créer le nouveau nœud sentinelle en utilisant l’allocateur du conteneur source avant de détruire le nœud sentinelle existant.

  • Les conteneurs ont été résolus pour toujours copier/déplacer/échanger les allocateurs en fonction de propagate_on_container_copy_assignment, propagate_on_container_move_assignment et propagate_on_container_swap, même pour les allocateurs déclarés is_always_equal.

  • Ajout de surcharges pour les fonctions de fusion de conteneurs et d’extraction de membres qui acceptent des conteneurs rvalue. Pour plus d’informations, consultez P0083 "Splicing Maps and Sets"

Traitement std::basic_istream::read de \r\n`` =>\n’

std::basic_istream::read a été corrigé pour ne pas écrire temporairement dans des parties de la mémoire tampon fournie lors du traitement de \r\n à \n. Ce changement offre une partie de l’avantage en matière de performances acquis dans Visual Studio 2017 15.8 pour les lectures supérieures à 4 ko. Toutefois, les améliorations de l’efficacité obtenues en évitant les trois appels virtuels par caractère restent présentes.

Constructeur std::bitset

Le constructeur std::bitset ne lit plus les uns et les zéros dans l’ordre inverse pour les grands bitsets.

Régression std::pair::operator=

Correction d’une régression dans l’opérateur d’affectation std::pair introduite lors de l’implémentation de LWG 2729 "Missing SFINAE on std::pair::operator=";. Maintenant, il accepte de nouveau les types convertibles en std::pair correctement.

Contextes non déduits pour add_const_t

Correction d’un bogue de traits de type mineur, où add_const_t et les fonctions associées sont censés être un contexte non déduit. En d’autres termes, add_const_t doit être un alias pour typename add_const<T>::type, pas const T.

Améliorations de la conformité dans la version 16.1

char8_t

P0482r6. C++20 ajoute un nouveau type de caractère qui est utilisé pour représenter les unités de code UTF-8. Les littéraux de chaîne u8 dans C++20 ont le type const char8_t[N] au lieu de const char[N], ce qui était le cas auparavant. Des changements similaires ont été proposés pour la norme C dans N2231. Vous trouverez des suggestions de correction de la compatibilité descendante de char8_t dans P1423r3. Le compilateur Microsoft C++ ajoute une prise en charge de char8_t dans Visual Studio 2019 version 16.1 quand vous spécifiez l’option de compilateur /Zc:char8_t. Il peut être rétabli au comportement C++17 via /Zc:char8_t-. Le compilateur EDG qui alimente IntelliSense ne le prend pas encore en charge dans Visual Studio 2019 version 16.1. Il se peut que vous voyiez des erreurs IntelliSense parasites qui n’affectent pas la compilation réelle.

Exemple

const char* s = u8"Hello"; // C++17
const char8_t* s = u8"Hello"; // C++20

Métafonction std::type_identity et objet de fonction std::identity

P0887R1 type_identity. L’extension de modèle de classe std::identity dépréciée a été supprimée et remplacée par la métafonction std::type_identity et l’objet de fonction std::identity C++20. Les deux sont disponibles uniquement sous /std:c++latest (/std:c++20 dans Visual Studio 2019 versions 16.11 et ultérieures).

L’exemple suivant génère l’avertissement de dépréciation C4996 pour std::identity (défini dans <type_traits>) dans 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);

L’exemple suivant montre comment utiliser le nouveau std::identity (défini dans <functional>) avec le nouveau 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);

Vérifications syntaxiques pour les expressions lambda génériques

Le nouveau processeur lambda active des vérifications syntaxiques en mode de conformité dans des processeurs lambda génériques, sous /std:c++latest (/std:c++20 dans Visual Studio 2019 versions 16.11 et ultérieures) ou sous tout autre mode de langage avec /Zc:lambda dans Visual Studio 2019 version 16.9 ou ultérieure (précédemment disponible comme /experimental:newLambdaProcessor à partir de Visual Studio 2019 version 16.3).

Le processeur lambda hérité compile cet exemple sans avertissement, mais le nouveau processeur lambda génère l’erreur C2760 :

void f() {
    auto a = [](auto arg) {
        decltype(arg)::Type t; // C2760 syntax error: unexpected token 'identifier', expected ';'
    };
}

Cet exemple montre la syntaxe correcte, maintenant appliquée par le compilateur :

void f() {
    auto a = [](auto arg) {
        typename decltype(arg)::Type t;
    };
}

Recherche dépendante des arguments pour les appels de fonction

P0846R0 (C++20) Possibilité accrue de trouver des modèles de fonction par le biais d’une recherche dépendante des arguments pour les expressions d’appel de fonction avec des arguments de modèle explicites. Nécessite /std:c++latest (ou /std:c++20 dans Visual Studio 2019 versions 16.11 et ultérieures).

Initialisation désignée

P0329R4 (C++20) L’initialisation désignée autorise la sélection de membres spécifiques dans l’initialisation d’agrégats en utilisant la syntaxe Type t { .member = expr }. Nécessite /std:c++latest (ou /std:c++20 dans Visual Studio 2019 versions 16.11 et ultérieures).

Classement de conversion d’énumération en son type sous-jacent fixe

Le compilateur classe désormais les conversions d’énumération conformément à N4800 11.3.3.2 Ranking implicit conversion sequences (4.2) :

  • Une conversion qui promeut une énumération dont le type sous-jacent est fixe à son type sous-jacent est meilleure qu’une conversion qui promeut au type sous-jacent promu, si les deux diffèrent.

Ce classement de conversion n’a pas été implémenté correctement avant Visual Studio 2019 version 16.1. Le comportement conforme peut modifier le comportement de résolution de surcharge ou exposer une ambiguïté où un comportement n’a pas été détecté précédemment.

Ce changement de comportement du compilateur s’applique à tous les modes /std et est un changement cassant tant de source que de binaire.

L’exemple suivant montre comment le comportement du compilateur change dans les versions 16.1 et ultérieures :

#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{});
}

Fonctions de bibliothèque standard nouvelles et mises à jour (C++20)

  • starts_with() et ends_with() pour basic_string et basic_string_view.
  • contains() pour les conteneurs associatifs.
  • remove(), remove_if() et unique() pour list et forward_list retournent maintenant size_type.
  • Ajout de shift_left() et shift_right() à <algorithm>.

Améliorations de la conformité dans la version 16.2

Fonctions noexceptconstexpr

Les fonctions constexpr ne sont plus considérées comme noexcept par défaut quand elles sont utilisées dans une expression constante. Ce changement de comportement provient de la résolution du CWG (Core Working Group) CWG 1351 et est activé dans /permissive-. L’exemple suivant se compile dans Visual Studio 2019 versions 16.1 et antérieures, mais génère une erreur C2338 dans Visual Studio 2019 version 16.2 :

constexpr int f() { return 0; }

int main() {
    static_assert(noexcept(f()), "f should be noexcept"); // C2338 in 16.2
}

Pour corriger l’erreur, ajoutez l’expression noexcept à la déclaration de fonction :

constexpr int f() noexcept { return 0; }

int main() {
    static_assert(noexcept(f()), "f should be noexcept");
}

Expressions binaires avec différents types d’énumération

C++20 a déconseillé les conversions arithmétiques habituelles sur les opérandes, où :

  • Un opérande est de type énumération, et

  • l’autre est d’un type énumération différent ou d’un type à virgule flottante.

Pour plus d’informations, consultez P1120R0.

Dans Visual Studio 2019 versions 16.2 et ultérieures, le code suivant génère un avertissement de niveau 4 C5054 lorsque l’option de compilateur /std:c++latest est activée (/std:c++20 dans Visual Studio 2019 versions 16.11 et ultérieures) :

enum E1 { a };
enum E2 { b };
int main() {
    int i = a | b; // warning C5054: operator '|': deprecated between enumerations of different types
}

Pour éviter l’avertissement, utilisez static_cast pour convertir le deuxième opérande :

enum E1 { a };
enum E2 { b };
int main() {
  int i = a | static_cast<int>(b);
}

L’utilisation d’une opération binaire entre un type énumération et un type à virgule flottante est désormais un avertissement de niveau 1 C5055 lorsque l’option du compilateur /std:c++latest est activée (/std:c++20 dans Visual Studio 2019 version 16.11 et ultérieure) :

enum E1 { a };
int main() {
  double i = a * 1.1;
}

Pour éviter l’avertissement, utilisez static_cast pour convertir le deuxième opérande :

enum E1 { a };
int main() {
   double i = static_cast<int>(a) * 1.1;
}

Comparaisons d’égalité relationnelles de tableaux

Les comparaisons d’égalité et relationnelles entre deux opérandes de type tableau sont déconseillées dans C++20 (P1120R0). En d’autres termes, une opération de comparaison entre deux tableaux (malgré des similitudes de classement et d’étendue) est désormais un avertissement. Dans Visual Studio 2019 versions 16.2 et ultérieures, le code suivant génère un avertissement de niveau 1 C5056 lorsque l’option de compilateur /std:c++latest est activée (/std:c++20 dans Visual Studio 2019 versions 16.11 et ultérieures) :

int main() {
    int a[] = { 1, 2, 3 };
    int b[] = { 1, 2, 3 };
    if (a == b) { return 1; } // warning C5056: operator '==': deprecated for array types
}

Pour éviter l’avertissement, vous pouvez comparer les adresses des premiers éléments :

int main() {
    int a[] = { 1, 2, 3 };
    int b[] = { 1, 2, 3 };
    if (&a[0] == &b[0]) { return 1; }
}

Pour déterminer si le contenu de deux tableaux est identique, utilisez la fonction std::equal :

std::equal(std::begin(a), std::end(a), std::begin(b), std::end(b));

Effet de la définition de l’opérateur spaceship sur == et !=

Une définition de l’opérateur de spaceship (<=>) seule n’a plus pour effet de réécrire les expressions incluant == ou !=, sauf si l’opérateur est marqué comme = default (P1185R2). L’exemple suivant se compile dans Visual Studio 2019 RTW et version 16.1, mais génère une erreur C2678 dans Visual Studio 2019 version 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
}

Pour éviter l’erreur, définissez operator== ou déclarez-la comme valeur par défaut :

#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;
}

Améliorations apportées à la bibliothèque standard

  • <charconv>to_chars() avec précision fixe/scientifique. (la précision générale est actuellement planifiée pour la version 16.4.)
  • P0020R6 : atomic<float>, atomic<double>, atomic<long double>
  • P0463R1 : endian
  • P0482R6 : Prise en charge de la bibliothèque pour char8_t
  • P0600R1 : [[nodiscard]] pour le STL, partie 1
  • P0653R2 : to_address()
  • P0754R2 : <version>
  • P0771R1 : noexcept Pour le constructeur move de std::function

Comparateurs const pour conteneurs associatifs

Le code pour la recherche et l’insertion dans set, map, multiset et multimap a été fusionné pour en réduire la taille. Les opérations d’insertion appellent désormais la comparaison inférieur à sur un foncteur de comparaison const, de la même façon que les opérations de recherche le faisaient précédemment. Le code suivant se compile dans Visual Studio 2019 versions 16.1 et versions antérieures, mais déclenche l’erreur C3848 dans Visual Studio 2019 version 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);

}

Pour éviter l’erreur, effectuez l’opérateur de comparaison const :

struct Comparer  {
   bool operator() (K a, K b) const {
      return a.a < b.a;
   }
};

Améliorations de la conformité dans Visual Studio 2019 version 16.3

Suppression des opérateurs d’extraction de flux pour char*

Les opérateurs d’extraction de flux pour pointeur vers caractères ont été supprimés et remplacés par des opérateurs d’extraction pour tableau de caractères (conformément à P0487R1). WG21 considère que les surcharges supprimées sont non sécurisées. En mode /std:c++20 ou /std:c++latest, l’exemple suivant génère désormais l’erreur 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)
}

Pour éviter l’erreur, utilisez l’opérateur d’extraction avec une variable char[] :

#include <iostream>
#include <iomanip>

int main() {
    char x[42];
    std::cin >> std::setw(42);
    std::cin >> x;  // OK
}

Nouveaux mots clés requires et concept

Les nouveaux mots clés requires et concept ont été ajoutés au compilateur Microsoft C++. Si vous tentez d’utiliser un comme identificateur en mode /std:c++20ou /std:c++latest, le compilateur lève l’erreur C2059 pour indiquer une erreur de syntaxe.

Constructeurs interdits en tant que noms de type

Le compilateur ne considère plus les noms de constructeur comme noms de classe injectés dans ce cas : quand ils apparaissent dans un nom qualifié après un alias pour une spécialisation de modèle de classe. Auparavant, les constructeurs étaient utilisables comme noms de type pour déclarer d’autres entités. L’exemple suivant génère désormais l’erreur C3646 :

#include <chrono>

class Foo {
   std::chrono::milliseconds::duration TotalDuration{}; // C3646: 'TotalDuration': unknown override specifier
};

Pour éviter l’erreur, déclarez TotalDuration comme indiqué ici :

#include <chrono>

class Foo {
  std::chrono::milliseconds TotalDuration {};
};

Vérification plus stricte des fonctions extern "C"

Si une extern "C" fonction a été déclarée dans différents espaces de noms, les versions précédentes du compilateur Microsoft C++ ne vérifiaient pas si les déclarations étaient compatibles. Dans Visual Studio 2019 versions 16.3 et ultérieures, le compilateur vérifie la compatibilité. En mode /permissive-, le code suivant génère des erreurs C2371 et 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

Pour éviter les erreurs dans l’exemple précédent, utilisez bool plutôt que BOOL dans les deux déclarations de f.

Améliorations apportées à la bibliothèque standard

Les en-têtes non standard <stdexcpt.h> et <typeinfo.h> ont été supprimés. Le code qui les inclut doit à la place inclure les en-têtes standard respectivement <exception> et <typeinfo>.

Améliorations de la conformité dans Visual Studio 2019 version 16.4

Meilleure application de la recherche de noms en deux phases pour les ID qualifiés dans /permissive-

La recherche de nom en deux phases nécessite que les noms non dépendants utilisés dans les corps de modèle soient visibles par le modèle au moment de la définition. Auparavant, ces noms pouvaient être trouvés une fois le modèle instancié. Cette modification facilite l’écriture de code portable et conforme dans MSVC sous l’indicateur /permissive-.

Dans Visual Studio 2019 version 16.4 avec l’indicateur /permissive- défini, l’exemple suivant génère une erreur, car N::f elle n’est pas visible lorsque le modèle f<T> est défini :

template <class T>
int f() {
    return N::f() + T{}; // error C2039: 'f': is not a member of 'N'
}

namespace N {
    int f() { return 42; }
}

En règle générale, cette erreur peut être corrigée en incluant les en-têtes ou les fonctions ou variables de déclaration anticipée manquants, comme illustré dans l’exemple suivant :

namespace N {
    int f();
}

template <class T>
int f() {
    return N::f() + T{};
}

namespace N {
    int f() { return 42; }
}

Conversion implicite d’expressions constantes intégrales en pointeur null

Le compilateur MSVC implémente désormais CWG Issue 903 en mode de conformité (/permissive-). Cette règle interdit la conversion implicite d’expressions constantes intégrales (à l’exception du littéral entier « 0 ») en constantes de pointeur null. L’exemple suivant génère l’erreur C2440 en mode de conformité :

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 *'
}

Pour corriger cette erreur, utilisez nullptr à la place de false. Un littéral 0 est toujours autorisé :

int* f(bool* p) {
    p = nullptr; // OK
    p = 0; // OK
    return nullptr; // OK
}

Règles standard pour les types de littéraux entiers

En mode de conformité (activé par /permissive-), MSVC utilise les règles standard pour les types de littéraux entiers. Les littéraux décimaux trop volumineux pour tenir dans un signed int recevaient précédemment le type .unsigned int Désormais, de tels littéraux reçoivent le type entier signed le plus grand suivant, long long. En outre, les littéraux avec le suffixe « ll » qui sont trop volumineux pour tenir dans un type signed reçoivent le type unsigned long long.

Cette modification peut entraîner la génération de différents diagnostics d’avertissement, et des différences de comportement pour des opérations arithmétiques sur des littéraux.

L’exemple suivant montre le nouveau comportement dans Visual Studio 2019 version 16.4. La variable i étant désormais de type unsigned int, l’avertissement est déclenché. Les bits d’ordre haut de la variable j sont définis sur 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
}

L’exemple suivant montre comment conserver l’ancien comportement, ainsi qu’éviter les avertissements et le changement de comportement d’exécution :

void f(int r) {
int i = 2964557531u; // OK
long long j = (long long)0x8000000000000000ll >> r; // shift will keep high-order bits
}

Paramètres de fonction occultant des paramètres de modèle

Le compilateur MSVC lève désormais une erreur quand un paramètre de fonction occulte un paramètre de modèle :

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);
}

Pour corriger l’erreur, modifiez le nom de l’un des paramètres :

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);
}

Spécialisations de traits de type fournies par l’utilisateur

Conformément à la sous-clause meta.rqmts de la Norme, le compilateur MSVC lève désormais une erreur quand il trouve une spécialisation définie par l’utilisateur de l’un des modèles type_traits spécifiés dans l’espace de noms std. Sauf indication contraire, de telles spécialisations entraînent un comportement non défini. L’exemple suivant a un comportement non défini parce qu’il enfreint la règle, et le static_assert échoue avec l’erreur C2338.

#include <type_traits>
struct S;

template<>
struct std::is_fundamental<S> : std::true_type {};

static_assert(std::is_fundamental<S>::value, "fail");

Pour éviter l’erreur, définissez un struct qui hérite du type_trait préféré et spécialisez-le :

#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");

Modifications apportées aux opérateurs de comparaison fournis par le compilateur

Le compilateur MSVC implémente désormais les modifications suivantes apportées aux opérateurs de comparaison conformément à P1630R1 lorsque l’option /std:c++latest ou /std:c++20 est activée :

Le compilateur ne réécrit plus d’expressions en utilisant operator== s’ils impliquent un type de retour qui n’est pas un bool. Le code suivant génère désormais l’erreur 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
}

Pour éviter l’erreur, vous devez définir explicitement l’opérateur nécessaire :

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;
}

Le compilateur ne définit plus d’opérateur de comparaison par défaut s’il est membre d’une classe de type union. L’exemple suivant génère désormais l’erreur 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;
}

Pour éviter l’erreur, définissez un corps pour l’opérateur :

#include <compare>

union S {
    int a;
    char b;
    auto operator<=>(const S&) const { ... }
};

bool lt(const S& lhs, const S& rhs) {
    return lhs < rhs;
}

Le compilateur ne définit plus d’opérateur de comparaison par défaut si la classe contient un membre de référence. Le code suivant génère désormais l’erreur C2120 :

#include <compare>

struct U {
    int& a;
    auto operator<=>(const U&) const = default;
};

bool lt(const U& lhs, const U& rhs) {
    return lhs < rhs;
}

Pour éviter l’erreur, définissez un corps pour l’opérateur :

#include <compare>

struct U {
    int& a;
    auto operator<=>(const U&) const { ... };
};

bool lt(const U& lhs, const U& rhs) {
    return lhs < rhs;
}

Améliorations de la conformité dans Visual Studio 2019 version 16.5

La déclaration de spécialisation explicite sans initialiseur n’est pas une définition

Sous /permissive-, MSVC applique désormais une règle standard en vertu de laquelle les déclarations de spécialisation explicites sans initialiseurs ne sont pas des définitions. Auparavant, la déclaration était considérée comme une définition avec un initialiseur par défaut. L’effet est observable au moment de la liaison, car un programme dépendant de ce comportement peut désormais avoir des symboles non résolus. Cet exemple génère désormais une erreur :

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.

Pour résoudre le problème, ajoutez un initialiseur :

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;
}

La sortie du préprocesseur conserve les nouvelles lignes

Le préprocesseur expérimental conserve désormais des nouvelles lignes et des espaces blancs lors de l’utilisation de /P ou /E avec /experimental:preprocessor.

Étant donné cet exemple de source,

#define m()
line m(
) line

La sortie précédente de /E était :

line line
#line 2

La nouvelle sortie de /E est désormais :

line
 line

les mots clés import et module dépendent du contexte

Conformément à P1857R1, les directives de préprocesseur import et module ont de nouvelles restrictions sur leur syntaxe. Cet exemple ne se compile plus :

import // Invalid
m;     // error C2146: syntax error: missing ';' before identifier 'm'

Pour résoudre le problème, conservez l’importation sur la même ligne :

import m; // OK

Suppression de std::weak_equality et std::strong_equality

La fusion de P1959R0 nécessite que le compilateur supprime le comportement et les références aux types std::weak_equality et std::strong_equality.

Le code dans cet exemple ne se compile plus :

#include <compare>

struct S {
    std::strong_equality operator<=>(const S&) const = default;
};

void f() {
    nullptr<=>nullptr;
    &f <=> &f;
    &S::operator<=> <=> &S::operator<=>;
}

L’exemple entraîne désormais ces erreurs :

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'

Pour résoudre le problème, mettez à jour pour préférer les opérateurs relationnels intégrés et remplacer les types supprimés :

#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<=>;
}

Modifications de TLS Guard

Auparavant, les variables locales de thread dans les DLL n’étaient pas correctement initialisées. À part sur le thread qui chargeait la DLL, elles n’étaient pas initialisées avant la première utilisation sur les threads qui existaient avant le chargement de la DLL. Ce défaut a désormais été corrigé. Les variables locales de thread dans une telle DLL sont initialisées immédiatement avant leur première utilisation sur de tels threads.

Ce nouveau comportement de test pour initialisation sur des utilisations de variables locales de thread peut être désactivé à l’aide de l’option de compilateur /Zc:tlsGuards-. Ou bien en ajoutant l’attribut [[msvc:no_tls_guard]] à des variables locales de thread particulières.

Meilleur diagnostic d’appel à des fonctions supprimées

Notre compilateur était plus permissif sur les appels à des fonctions précédemment supprimées. Par exemple, si les appels se sont produits dans le contexte d’un corps de modèle, nous ne diagnostiquions pas l’appel. En outre, s’il y avait plusieurs instances d’appels à des fonctions supprimées, nous n’émettrons qu’un seul diagnostic. Nous émettons maintenant un diagnostic pour chacun d’eux.

Une conséquence du nouveau comportement peut produire un petit changement cassant : le code qui a appelé une fonction supprimée ne sera pas diagnostiqué s’il n’a jamais été nécessaire pour la génération de code. Désormais, nous le diagnostiquons en amont.

Cet exemple montre le code qui génère désormais une erreur :

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

Pour résoudre le problème, supprimez les appels à des fonctions supprimées :

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 };

Améliorations de la conformité dans Visual Studio 2019 version 16.6

Les flux de bibliothèque standard rejettent les insertions de types de caractères mal codés

Traditionnellement, l’insertion d’un wchar_t dans un std::ostream, et l’insertion de char16_t ou char32_t dans un std::ostream ou std::wostream, génèrent leur valeur intégrale. L’insertion de pointeurs vers ces types de caractères génère la valeur du pointeur. Les programmeurs ne trouvent aucun des deux cas intuitifs. Ils s’attendent souvent à ce que la bibliothèque standard transcode le caractère ou la chaîne de caractères terminée par null à la place, et génère le résultat.

La proposition C++20 P1423R3 ajoute, pour ces combinaisons de flux et de caractères ou de types de pointeur de caractères, des surcharges d’opérateur d’insertion de flux supprimé. Sous /std:c++20 ou /std:c++latest, les surcharges rendent ces insertions mal formées, au lieu de se comporter d’une manière probablement non intentionnelle. Le compilateur lève l’erreur C2280 quand il en trouve une. Vous pouvez définir la macro de « hachure d’échappement » _HAS_STREAM_INSERTION_OPERATORS_DELETED_IN_CXX20 sur 1 pour restaurer l’ancien comportement. (La proposition supprime également les opérateurs d’insertion de flux pour char8_t. Notre bibliothèque standard implémentait des surcharges similaires lorsque nous avons ajouté la prise en charge de char8_t, de sorte que le comportement « incorrect » n’a jamais été disponible pour char8_t.)

Cet exemple montre le comportement avec cette modification :

#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';
}

Le code génère désormais ces messages de diagnostic :

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

Vous pouvez obtenir l’effet de l’ancien comportement dans tous les modes de langage en convertissant des types de caractères en unsigned intou des types de pointeurs vers caractère en 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"
}

Type de retour de std::pow() pour std::complex modifié

Auparavant, l’implémentation par MSVC des règles de promotion pour le type de retour du modèle de fonction std::pow() était incorrecte. Par exemple, précédemment pow(complex<float>, int) retournait complex<float>. Maintenant, il retourne correctement complex<double>. Le correctif a été implémenté sans condition pour tous les modes de normes dans Visual Studio 2019 version 16.6.

Cette modification peut entraîner des erreurs du compilateur. Par exemple, précédemment, vous pouviez multiplier pow(complex<float>, int) par un float. Étant donné que complex<T> operator* attend des arguments du même type, l’exemple suivant émet désormais une erreur de compilateur 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

Il existe de nombreux correctifs possibles :

  • Remplacez le type du multiplicande float par double. Cet argument peut être converti directement en un complex<double> pour correspondre au type retourné par pow.

  • Réduisez le résultat de pow à complex<float> en déclarant complex<float>{pow(ARG, ARG)}. Vous pouvez ensuite continuer à multiplier par une valeur float.

  • Passez float au lieu de int à pow. Cette opération peut être plus lente.

  • Dans certains cas, vous pouvez éviter pow entièrement. Par exemple, pow(cf, -1) peut être remplacé par division.

Avertissements switch pour C

Dans Visual Studio 2019 versions 16.6 et ultérieures, le compilateur implémente des avertissements C++ préexistants pour le code compilé en tant que C. Les avertissements suivants sont désormais activés à différents niveaux : C4060, C4061, C4062, C4063, C4064, C4065, C4808 et C4809. Les avertissements C4065 et C4060 sont désactivés par défaut dans C.

Les avertissements se déclenchent sur des instructions case manquantes, des enum non définies et des instructions switch bool incorrectes (autrement dit, qui contiennent trop de cas). Par exemple :

#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
    }
}

Pour corriger ce code, supprimez le cas default redondant :

#include <stdbool.h>

int main() {
    bool b = true;
    switch (b) {
        case true: break;
        case false: break;
    }
}

Classes non nommées dans les déclarations typedef

Dans Visual Studio 2019 versions 16.6 et ultérieures, le comportement des déclarations typedef a été limité pour se conformer à P1766R1. Avec cette mise à jour, les classes sans nom dans une déclaration typedef ne peuvent pas avoir de membres autres que :

  • membres de données non statiques sans initialiseurs membres par défaut,
  • classes membres, ou
  • énumérations membres.

Les mêmes restrictions sont appliquées de manière récursive à chaque classe imbriquée. La restriction est destinée à garantir la simplicité des structs qui ont des noms typedef à des fins de liaison. Ils doivent être suffisamment simples pour qu’aucun calcul de liaison ne soit nécessaire avant que le compilateur accède au nom typedef pour la liaison.

Cette modification affecte tous les modes de normes du compilateur. Dans les modes par défaut (/std:c++14) et /std:c++17, le compilateur émet un avertissement C5208 pour code non conforme. Si /permissive- est spécifiée, le compilateur émet l’avertissement C5208 en tant qu’erreur sous /std:c++14, et l’erreur C7626 sous /std:c++17. Le compilateur émet l’erreur C7626 pour code non conforme quand /std:c++20 ou /std:c++latest est spécifié.

L’exemple suivant montre les constructions qui ne sont plus autorisées dans des structs non nommés. Selon le mode standard spécifié, des erreurs ou avertissements C5208 ou C7626 sont émis :

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;

Le code ci-dessus peut être résolu en donnant un nom à la classe non nommée :

struct B { };
typedef struct S_ : B {
    void f();
    static int i;
    struct U {
        void f();
    };
    int j = 10;
} S;

Importation d’argument par défaut dans C++/CLI

Un nombre croissant d’API ont des arguments par défaut dans .NET Core. Par conséquent, nous prenons désormais en charge l’importation d’argument par défaut dans C++/CLI. Cette modification peut interrompre le code existant où plusieurs surcharges sont déclarées, comme dans cet exemple :

public class R {
    public void Func(string s) {}   // overload 1
    public void Func(string s, string s2 = "") {} // overload 2;
}

Lorsque cette classe est importée dans C++/CLI, un appel à l’une des surcharges provoque une erreur :

    (gcnew R)->Func("abc"); // error C2668: 'R::Func' ambiguous call to overloaded function

Le compilateur émet une erreur C2668, car les deux surcharges correspondent à cette liste d’arguments. Dans la deuxième surcharge, le deuxième argument est rempli par l’argument par défaut. Pour contourner ce problème, vous pouvez supprimer la surcharge redondante (1). Vous pouvez également utiliser la liste d’arguments complète et fournir explicitement les arguments par défaut.

Améliorations de la conformité dans Visual Studio 2019 version 16.7

Définition de is trivially copyable

C++20 a changé la définition de is trivially copyable. Quand une classe a un membre de données non statique avec un type qualifié volatile, elle n’implique plus que tout constructeur de copie ou de déplacement généré par le compilateur, ou opérateur d’affectation de copie ou de déplacement, n’est pas trivial. Le comité de la Norme C++ a appliqué cette modification de manière rétroactive en tant que rapport de défaut. Dans MSVC, le comportement du compilateur ne change pas dans différents modes de langage, tels que /std:c++14 ou /std:c++latest.

Voici un exemple du nouveau comportement :

#include <type_traits>

struct S
{
    volatile int m;
};

static_assert(std::is_trivially_copyable_v<S>, "Meow!");

Ce code ne se compile pas dans les versions de MSVC antérieures à Visual Studio 2019 version 16.7. Il existe un avertissement du compilateur désactivé par défaut que vous pouvez utiliser pour détecter cette modification. Si vous compilez le code ci-dessus en utilisant cl /W4 /w45220, vous verrez l’avertissement suivant :

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`

Les conversions de pointeur vers membre et de littéral de chaîne en bool sont restrictives

Le comité de la Norme C++ a récemment adopté le Rapport de défaut P1957R2, qui considère la conversion de T* en bool comme restrictive. MSVC a résolu un bogue dans son implémentation, qui diagnostiquait précédemment la conversion de T* en bool comme restrictive, mais ne diagnostiquait pas la conversion d’un littéral de chaîne en bool ou d’un pointeur vers membre en bool.

Le programme suivant est mal formé dans Visual Studio 2019 version 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
}

Pour corriger ce code, ajoutez des comparaisons explicites à nullptr, ou évitez les contextes où les conversions restrictives sont mal formées :

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 n’est convertible qu’en bool en tant qu’initialisation directe

Dans C++11, nullptr n’est convertible qu’en bool en tant que conversion directe ; par exemple, lorsque vous initialisez un bool à l’aide d’une liste d’ initialiseurs entre accolades. Cette restriction n’a jamais été appliquée par MSVC. MSVC implémente désormais la règle sous /permissive-. Les conversions implicites sont désormais diagnostiquées comme mal formées. Une conversion contextuelle en bool est toujours autorisée, car l’initialisation directe bool b(nullptr) est valide.

Dans la plupart des cas, l’erreur peut être corrigée en remplaçant nullptr par false, comme illustré dans cet exemple :

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
}

Comportement d’initialisation conforme pour les initialisations de tableaux avec des initialiseurs manquants

Auparavant, MSVC avait un comportement non conforme pour les initialisations de tableaux qui avaient des initialiseurs manquants. MSVC appelait toujours le constructeur par défaut pour chaque élément de tableau qui n’avait pas d’initialiseur. Le comportement standard consiste à initialiser chaque élément avec une liste d’initialiseurs entre accolades ({}). Le contexte d’initialisation d’une liste d’initialiseurs entre accolades vide est l’initialisation de copie, qui n’autorise pas les appels à des constructeurs explicites. Il peut également y avoir des différences de runtime, car l’utilisation de {} pour initialiser peut appeler un constructeur qui prend un std::initializer_list, au lieu du constructeur par défaut. Le comportement conforme est activé sous /permissive-.

Voici un exemple de comportement modifié :

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
}

L’initialisation de membres de classe avec des noms surchargés est correctement séquencée

Nous avons identifié un bogue dans la représentation interne des membres de données de classe quand un nom de type est également surchargé en tant que nom d’un membre de données. Ce bogue entraînait des incohérences dans l’ordre d’initialisation d’agrégats et d’initialisation de membres. Le code d’initialisation généré est désormais correct. Toutefois, cette modification peut entraîner des erreurs ou des avertissements dans une source qui s’est appuyée par inadvertance sur des membres mal ordonnés, comme dans cet exemple :

// 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
};

Dans les versions précédentes, le constructeur initialisait incorrectement le membre de données Inner avant le membre de données v. (La norme C++ nécessite un ordre d’initialisation identique à l’ordre de déclaration des membres). Maintenant que le code généré suit la norme, la liste des initialiseurs de membre est en désordre. Le compilateur génère un avertissement pour cet exemple. Pour le corriger, réorganisez la liste des initialiseurs de membre pour refléter l’ordre de déclaration.

Résolution de surcharge impliquant des surcharges intégrales et des arguments long

La norme C++ nécessite le classement d’une conversion de long en int en tant que conversion standard. Les compilateurs MSVC précédents la classent incorrectement comme une promotion intégrale, qui est mieux classée pour la résolution de surcharge. Ce classement peut amener une résolution de surcharge à résoudre avec succès quand elle devrait être considérée comme ambiguë.

Le compilateur considère désormais le classement correctement en mode /permissive-. Un code non valide est correctement diagnostiqué, comme dans cet exemple :

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
}

Vous pouvez résoudre ce problème de plusieurs façons :

  • Sur le site d’appel, modifiez le type de l’argument passé en int. Vous pouvez modifier le type de variable ou le caster.

  • S’il existe de nombreux sites d’appel, vous pouvez ajouter une autre surcharge qui prend un argument long. Dans cette fonction, castez et transférez l’argument à la surcharge int.

Utilisation d’une variable non définie avec une liaison interne

Les versions de MSVC antérieures à Visual Studio 2019 version 16.7 acceptaient l’utilisation d’une variable déclarée extern qui avait une liaison interne et n’était pas été définie. Ces variables ne peuvent pas être définies dans une autre unité de traduction et ne peuvent pas former un programme valide. Le compilateur diagnostique désormais ce cas au moment de la compilation. L’erreur est similaire à l’erreur pour les fonctions statiques non définies.

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.
}

Précédemment, ce programme compilait et liait correctement, mais émet désormais l’erreur C7631.

error C7631: 'anonymous-namespace::x': variable with internal linkage declared but not defined

De telles variables doivent être définies dans l’unité de traduction dans laquelle elles sont utilisées. Par exemple, vous pouvez fournir un initialiseur explicite ou une définition distincte.

Exhaustivité du type et conversions de pointeur dérivé en pointeur de base

Dans les normes C++ antérieures à C++20, une conversion d’une classe dérivée en une classe de base n’avait pas besoin que la classe dérivée soit un type de classe complet. Le comité de la norme C++ a approuvé un changement de rapport de défaut rétroactif qui s’applique à toutes les versions du langage C++. Cette modification aligne le processus de conversion avec des traits de type, tels que std::is_base_of, qui nécessitent que la classe dérivée soit un type de classe complet.

Voici un exemple :

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;
};

Ce changement de comportement s’applique à tous les modes de langage C++ de MSVC, pas seulement /std:c++20 ou /std:c++latest.

Les conversions restrictives sont diagnostiqués plus systématiquement

MSVC émet un avertissement pour les conversions restrictives dans un initialiseur de liste entre accolades. Auparavant, le compilateur ne diagnostiquait pas des conversions restrictives de types sous-jacents enum plus grands en types intégraux plus étroits. (Le compilateur les considérait incorrectement comme une promotion intégrale plutôt qu’une conversion). Si la conversion restrictive est intentionnelle, vous pouvez éviter l’avertissement en utilisant un static_cast sur l’argument d’initialiseur. Vous pouvez également choisir un type intégral de destination plus grand.

Voici un exemple d’utilisation d’un static_cast explicite pour traiter l’avertissement :

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
}

Améliorations de la conformité dans Visual Studio 2019 version 16.8

Extension « Class rvalue used as lvalue »

MSVC a une extension qui permet d’utiliser une valeur rvalue de classe en tant que lvalue. L’extension n’étend pas la durée de vie de la rvalue de classe et peut entraîner un comportement non défini lors de l’exécution. Nous appliquons maintenant la règle standard et interdisons cette extension sous /permissive-. Si vous ne pouvez pas encore utiliser /permissive-, vous pouvez utiliser /we4238 pour interdire explicitement l’extension. Voici un exemple :

// 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
}

Extension « Explicit specialization in non-namespace scope »

MSVC avait une extension qui permettait une spécialisation explicite dans étendue non-espace de noms. Elle fait maintenant partie de la norme, après la résolution de CWG 727. Toutefois, il existe des différences de comportement. Nous avons ajusté le comportement de notre compilateur pour nous aligner sur la norme.

// 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"

Vérification des types de classe abstraite

La norme C++20 a modifié l’utilisation des compilateurs de processus pour détecter l’utilisation d’un type de classe abstrait en tant que paramètre de fonction. Plus précisément, il ne s’agit plus d’une erreur SFINAE. Auparavant, si le compilateur détectait qu’une spécialisation d’un modèle de fonction aurait une instance de type de classe abstraite comme paramètre de fonction, cette spécialisation était considérée comme mal formée. Elle n’était pas ajoutée à l’ensemble de fonctions candidates viables. Dans C++20, la vérification d’un paramètre de type de classe abstraite ne se produit pas tant que la fonction n’est pas appelée. Cela a pour effet que le code utilisé pour compiler n’entraîne pas d’erreur. Voici un exemple :

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);
}

Auparavant, l’appel à compare tentait de spécialiser le modèle de fonction compare en utilisant un argument de modèle String pour T. Il ne générait pas de spécialisation valide, car String est une classe abstraite. La seule fonction candidate viable était compare(const Node&, const Node&). Toutefois, sous C++20, la vérification du type de classe abstraite ne se produit pas tant que la fonction n’est pas appelée. Par conséquent, la spécialisation compare(String, String) est ajoutée à l’ensemble de fonctions candidates viables, et choisie comme meilleure candidate parce que la conversion de const String& en String est une meilleure séquence de conversion que la conversion de const String& en const Node&.

Sous C++20, un correctif possible pour cet exemple consiste à utiliser des concepts, autrement dit, à modifier la définition de compare en :

template<typename T>
int compare(T x, T y) requires !std::is_abstract_v<T>
{
    return x < y ? -1 : (x > y ? 1 : 0);
}

Ou bien, si les concepts C++ ne sont pas disponibles, vous pouvez revenir à 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);
}

Prise en charge de P0960R3 : Autoriser l’initialisation d’agrégats à partir d’une liste de valeurs entre parenthèses

C++20 P0960R3 ajoute la prise en charge de l’initialisation d’un agrégat à l’aide d’une liste d’initialiseurs entre parenthèses. Par exemple, le code suivant est valide dans C++20 :

struct S {
    int i;
    int j;
};

S s(1, 2);

Cette fonctionnalité est essentiellement additive, c’est-à-dire que le code qui ne se compilait pas précédemment se compile désormais. Toutefois, elle modifie le comportement de std::is_constructible. En mode C++17, cette static_assert échoue mais, en mode C++20, elle réussit :

static_assert(std::is_constructible_v<S, int, int>, "Assertion failed!");

Si vous utilisez cet trait de type pour contrôler la résolution de surcharge, cela peut entraîner un changement de comportement entre C++17 et C++20.

Résolution de surcharge impliquant des modèles de fonction

Auparavant, le compilateur permettait la compilation sous /permissive- de code qui ne devait pas se compiler. Cela avait pour effet que le compilateur appelait la mauvaise fonction, conduisant à un changement de comportement du 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);
}

L’appel à g utilise un jeu de surcharge qui contient deux fonctions, ::f et N::f. N::f étant un modèle de fonction, le compilateur traitait l’argument de fonction comme un contexte non déduit. Cela signifie dans ce cas que l’appel à g échouait parce que le compilateur ne pouvait pas déduire un type pour le paramètre de modèle T. Malheureusement, le compilateur ne tenait pas compte du fait qu’il avait déjà décidé que ::f convenait bien pour l’appel de fonction. Au lieu d’émettre une erreur, le compilateur générait du code pour appeler g en utilisant ::f en tant qu’argument.

Étant donné que, dans de nombreux cas, l’utilisation de ::f comme argument de fonction est ce que l’utilisateur attend, nous n’émettons une erreur que si le code est compilé avec /permissive-.

Migration de /await des coroutines C++20

Les coroutines C++20 standard sont désormais activées par défaut sous /std:c++20 et /std:c++latest. Elles diffèrent des de Coroutines TS et de la prise en charge sous l’option /await. La migration de /await vers des coroutines standard peut nécessiter des modifications de sources.

Mots clés non standard

Les anciens mots clés await et yield ne sont pas pris en charge en mode C++20. Le code doit utiliser co_await et co_yield à la place. Le mode standard n’autorise pas non plus l’utilisation de return dans une coroutine. Chaque return dans une coroutine doit utiliser co_return.

// /await
task f_legacy() {
    ...
    await g();
    return n;
}
// /std:c++latest
task f() {
    ...
    co_await g();
    co_return n;
}

Types de initial_suspend/final_suspend

Sous /await, les fonctions initiales de promesse et de suspension peuvent être déclarées comme retournant bool. Ce comportement n’est pas standard. Dans C++20, ces fonctions doivent retourner un type de classe pouvant être attendu, souvent l’un des types pouvant être attendus triviaux : std::suspend_always si la fonction a précédemment retourné true, ou std::suspend_never si elle a retourné 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{}; }
    ...
};

type de yield_value

Dans C++20, la fonction yield_value de promesse doit retourner un type pouvant être attendu. En mode /await, la fonction yield_value était autorisée à retourner void, et était toujours suspendue. De telles fonctions peuvent être remplacées par une fonction qui retourne 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{}; }
};

Fonction de gestion des exceptions

/await prend en charge un type de promesse sans fonction de gestion des exceptions ou avec une fonction de gestion des exceptions nommée set_exception qui accepte une std::exception_ptr. Dans C++20, le type de promesse doit avoir une fonction nommée unhandled_exception qui ne prend aucun argument. L’objet d’exception peut être obtenu à partir de std::current_exception si nécessaire.

// /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(); }
    ...
};

Types de retour déduits de coroutines non pris en charge

C++20 ne prend pas en charge les coroutines avec un type de retour qui inclut un type d’espace réservé tel que auto. Les types de retour de coroutines doivent être déclarés explicitement. Sous /await, ces types déduits impliquent toujours un type expérimental et nécessitent l’inclusion d’un en-tête qui définit le type requis : 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;
};

Type de retour de return_value

Le type de retour de la fonction return_value de promesse doit être void. En mode /await, le type de retour peut être n’importe quoi et est ignoré. Ce diagnostic peut aider à détecter des erreurs subtiles, comme quand l’auteur suppose erronément que la valeur de retour de return_value est retournée à un appelant.

// /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
};

Comportement de conversion d’objet de retour

Si le type de retour déclaré d’une coroutine ne correspond pas au type de retour de la fonction get_return_object de promesse, l’objet retourné par get_return_object est converti en type de retour de la coroutine. Sous /await, cette conversion est effectuée tôt, avant que le corps de la coroutine ait la possibilité de s’exécuter. Dans /std:c++20 ou /std:c++latest, cette conversion est effectuée lorsque la valeur est retournée à l’appelant. Elle permet aux coroutines qui ne s’interrompent pas au point de suspension initial d’utiliser l’objet retourné par get_return_object dans le corps de la coroutine.

Paramètres de promesse de coroutine

Dans C++20, le compilateur tente de passer les paramètres de coroutine (le cas échéant) à un constructeur du type de promesse. En cas d’échec, il réessaye avec un constructeur par défaut. En mode /await, seul le constructeur par défaut était utilisé. Cette modification peut entraîner une différence de comportement si la promesse a plusieurs constructeurs. Ou bien s’il existe une conversion d’un paramètre de coroutine vers le type de promesse.

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{});

Des modules /permissive- et C++20 sont activés par défaut sous /std:c++20

La prise en charge des modules C++20 est activée par défaut sous /std:c++20 et /std:c++latest. Pour plus d’informations sur cette modification et les scénarios dans lesquels module et import sont traités de manière conditionnelle en tant que mots clés, consultez Prise en charge des modules C++20 standard avec MSVC dans Visual Studio 2019 version 16.8.

En tant que prérequis pour la prise en charge des modules, permissive- est désormais activé quand /std:c++20 ou /std:c++latest sont spécifiés. Pour plus d’informations, consultez /permissive-.

Pour le code précédemment compilé sous /std:c++latest et nécessitant des comportements de compilateur non conformes, /permissive peut être spécifié pour désactiver le mode de conformité stricte dans le compilateur. L’option du compilateur doit apparaître après /std:c++latest dans la liste d’arguments de ligne de commande. Toutefois, /permissive génère une erreur si l’utilisation de Modules est détectée :

erreur C1214 : Des modules sont en conflit avec le comportement non standard demandé via « option »

Les valeurs les plus courantes pour l’option sont les suivantes :

Option Description
/Zc:twoPhase- Une recherche de noms en deux phases est requise pour les modules C++20, et impliquée par /permissive-.
/Zc:hiddenFriend- Des règles de recherche de noms d’ami masqués standard sont requises pour les modules C++20, et impliquées par /permissive-.
/Zc:lambda- Un traitement lambda standard est requis pour les modules C++20, et impliqué par le mode /std:c++20 ou version ultérieure.
/Zc:preprocessor- Le préprocesseur conforme est requis pour l’utilisation et la création de l’unité d’en-tête C++20 uniquement. Les modules nommés ne nécessitent pas cette option.

L’option /experimental:module est toujours nécessaire pour utiliser les modules std.* fournis avec Visual Studio, car ils ne sont pas encore normalisés.

L’option /experimental:module implique également /Zc:twoPhase, /Zc:lambda et /Zc:hiddenFriend. Auparavant, le code compilé avec des modules pouvait parfois être compilé avec /Zc:twoPhase- si le module était uniquement utilisé. Ce comportement n’est plus pris en charge.

Améliorations de la conformité dans Visual Studio 2019 version 16.9

Initialisation de copie d’élément temporaire dans une initialisation directe de référence

Le problème du Core Working Group CWG 2267 avait trait à une incohérence entre une liste d’initialiseurs entre parenthèses et une liste d’initialiseurs entre accolades. La résolution aligne les deux formes.

Visual Studio 2019 version 16.9 implémente le comportement modifié dans tous les modes de compilateur /std. Toutefois, étant donné qu’il s’agit potentiellement d’un changement cassant de source, il n’est pris en charge que si le code est compilé à l’aide de /permissive-.

Cet exemple montre le changement de comportement :

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
}

Caractéristiques de destructeur et sous-objets potentiellement construits

Le problème du Core Working Group CWG 2336 couvre une omission concernant des spécifications d’exception implicite de destructeurs de destructeurs dans les classes qui ont des classes de base virtuelles. L’omission signifiait qu’un destructeur dans une classe dérivée pouvait avoir une spécification d’exception plus faible qu’une classe de base, si cette base était abstraite et avait une base virtual.

Visual Studio 2019 version 16.9 implémente le comportement modifié dans tous les modes de compilateur /std.

Cet exemple montre comment l’interprétation a changé :

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);
};

Avant cette modification, le destructeur implicitement défini pour B était noexcept, car seuls des sous-objets potentiellement construits étaient pris en compte. Et la classe de base V n’est pas un sous-objet potentiellement construit, car il s’agit d’une base virtual et B est abstrait. Toutefois, la classe de base V est un sous-objet potentiellement construit de classe D, de sorte que D::~D est déterminée comme étant noexcept(false), conduisant à une classe dérivée avec une spécification d’exception plus faible que sa base. Cette interprétation est hasardeuse. Elle peut entraîner un comportement d’exécution incorrect si une exception est levée à partir d’un destructeur d’une classe dérivée de B.

Par ailleurs, avec ce changement, un destructeur peut lever une exception s’il a un destructeur virtuel, et si toute classe de base virtuelle a un destructeur pouvant lever une exception.

Types similaires et liaison de référence

Le problème du Core Working Group CWG 2352 a trait à une incohérence entre les règles de liaison de référence et les modifications apportées à la similarité de type. L’incohérence a été introduite dans des rapports de défauts précédents (par exemple, CWG 330). Cela a affecté Visual Studio 2019 versions 16.0 à 16.8.

Avec cette modification, à partir de Visual Studio 2019 version 16.9, le code qui liait précédemment une référence à un élément temporaire dans Visual Studio 2019 versions 16.0 à 16.8 peut désormais se lier directement quand les types impliqués diffèrent uniquement par des « cv-qualifiers ».

Visual Studio 2019 version 16.9 implémente le comportement modifié dans tous les modes de compilateur /std. Il s’agit potentiellement d’un changement cassant de source.

pour une modification associée, consultez Références aux types avec des qualificateurs cv non correspondants.

Cet exemple montre le comportement modifié :

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
}

La mise à jour peut modifier le comportement du programme qui s’appuyait sur un élément temporaire introduit :

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;
}

Changement de comportement des options /Zc:twoPhase et /Zc:twoPhase-

Normalement, les options du compilateur MSVC fonctionnent selon le principe que le dernier vu gagne. Malheureusement, ce n’était pas le cas avec les options /Zc:twoPhase et /Zc:twoPhase-. Ces options étaient « collantes », de sorte que des options ultérieures ne pouvaient pas les remplacer. Par exemple :

cl /Zc:twoPhase /permissive a.cpp

Dans ce cas, la première option /Zc:twoPhase active une recherche de nom en deux phases stricte. La deuxième option est destinée à désactiver le mode de conformité stricte (c’est l’inverse de /permissive-), mais elle n’a pas désactivé /Zc:twoPhase.

Visual Studio 2019 version 16.9 modifie ce comportement dans tous les modes de compilateur /std. Les options /Zc:twoPhase et /Zc:twoPhase- ne sont plus « collantes », et les options ultérieures peuvent les remplacer.

Spécificateurs noexcept explicites sur les modèles de destructeur

Le compilateur acceptait précédemment un modèle de destructeur déclaré avec une spécification d’exception ne pouvant pas être levée, mais défini sans spécificateur noexcept explicite. La spécification d’exception implicite d’un destructeur dépend des propriétés de la classe, c’est-à-dire des propriétés qui peuvent ne pas être connues au moment de la définition d’un modèle. La norme C++ nécessite également le comportement suivant : si un destructeur est déclaré sans spécificateur noexcept, il a une spécification d’exception implicite, et aucune autre déclaration de la fonction ne peut avoir de spécificateur noexcept.

Visual Studio 2019 version 16.9 change de comportement conforme dans tous les modes de compilateur /std.

Cet exemple montre le changement de comportement du compilateur :

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 { /* ... */ }

Expressions réécrites en C++20

Depuis Visual Studio 2019 version 16.2, sous /std:c++latest, le compilateur accepte du code comme cet exemple :

#include <compare>

struct S {
    auto operator<=>(const S&) const = default;
    operator bool() const;
};

bool f(S a, S b) {
    return a < b;
}

Toutefois, le compilateur n’appellerait pas la fonction de comparaison attendue par l’auteur. Le code ci-dessus aurait dû réécrire a < b en tant que (a <=> b) < 0. Au lieu de cela, le compilateur a utilisé la fonction de conversion définie par l’utilisateur operator bool() et comparé bool(a) < bool(b). Dans Visual Studio 2019 versions 16.9 et ultérieures, le compilateur réécrit l’expression à l’aide de l’expression d’opérateur shapescript attendue.

Changement cassant source

L’application correcte des conversions aux expressions réécrites a un autre effet : le compilateur diagnostique également correctement les ambiguïtés des tentatives de réécriture de l’expression. Examinez cet exemple :

struct Base {
    bool operator==(const Base&) const;
};

struct Derived : Base {
    Derived();
    Derived(const Base&);
    bool operator==(const Derived& rhs) const;
};

bool b = Base{} == Derived{};

Dans C++17, ce code serait accepté en raison de la conversion de pointeur dérivé en pointeur de base de Derived sur le côté droit de l’expression. Dans C++20, l’expression synthétisée candidate est également ajoutée : Derived{} == Base{}. En raison des règles de la norme concernant la fonction gagnante sur la base des conversions, il s’avère que le choix entre Base::operator== et Derived::operator== est indécidable. Étant donné que les séquences de conversion dans les deux expressions ne sont pas meilleures ou pires l’une que l’autre, l’exemple de code entraîne une ambiguïté.

Pour résoudre l’ambiguïté, ajoutez une nouvelle fonction candidate qui ne sera pas soumise aux deux séquences de conversion :

bool operator==(const Derived&, const Base&);

Changement cassant du runtime

En raison des règles de réécriture d’opérateur en C++20, il est possible que la résolution de surcharge trouve une nouvelle fonction candidate qu’elle ne trouve pas dans un mode de langage inférieur. Et la nouvelle fonction candidate peut être plus appropriée que la plus ancienne. Examinez cet exemple :

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; }
};

Dans C++17, la seule fonction candidate pour ci == *this est const_iterator::operator==. Il s’agit d’une correspondance, car *this passe par une conversion de pointeur dérivé en pointeur de base à const_iterator. Dans C++20, une autre fonction candidate réécrite est ajoutée,*this == ci, qui appelle iterator::operator==. Cette fonction candidate ne nécessitant aucune conversion, elle est plus appropriée que const_iterator::operator==. Le problème avec la nouvelle fonction candidate est qu’il s’agit de la fonction en cours de définition, de sorte que la nouvelle sémantique de la fonction provoque une définition infiniment récursive de iterator::operator==.

Pour aider dans un code comme celui de l’exemple, le compilateur implémente un nouvel avertissement :

$ 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

Pour corriger le code, soyez explicite sur la conversion à utiliser :

struct iterator {
  bool operator==(const const_iterator &ci) const { return ci == static_cast<const const_iterator&>(*this); }
};

Améliorations de la conformité dans Visual Studio 2019 version 16.10

Surcharge incorrecte choisie pour l’initialisation de copie de la classe

Étant donné cet exemple de code :

struct A { template <typename T> A(T&&); };
struct B { operator A(); };
struct C : public B{};
void f(A);
f(C{});

Les versions antérieures du compilateur convertissaient incorrectement l’argument de f du type C en un A en utilisant le constructeur de conversion basé sur un modèle de A. C++ standard nécessite l’utilisation de l’opérateur de conversion B::operator A à la place. Dans Visual Studio 2019 versions 16.10 et ultérieures, le comportement de résolution de surcharge est modifié pour utiliser la surcharge correcte.

Cette modification peut également corriger la surcharge choisie dans d’autres situations :

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)
    {
        // ...
    }
}

Analyse incorrecte des littéraux à virgule flottante

Dans Visual Studio 2019 versions 16.10 et ultérieure, les littéraux à virgule flottante sont analysés en fonction de leur type réel. Les versions antérieures du compilateur analysaient toujours un littéral à virgule flottante comme s’il avait un type double, puis convertissaient le résultat en type réel. Ce comportement pouvait conduire à un arrondi et un rejet incorrects de valeurs valides :

// 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;

Point de déclaration incorrect

Les versions antérieures du compilateur ne pouvaient pas compiler du code autoréférentiel comme cet exemple :

struct S {
    S(int, const S*);

    int value() const;
};

S s(4, &s);

Le compilateur ne déclarait pas la variable s tant qu’il n’avait pas analysé la déclaration entière, y compris les arguments du constructeur. La recherche du s dans la liste d’arguments du constructeur échouait. Dans Visual Studio 2019 versions 16.10 et ultérieures, cet exemple se compile désormais correctement.

Malheureusement, ce changement peut casser du code existant, comme dans cet exemple :

S s(1, nullptr); // outer s
// ...
{
   S s(s.value(), nullptr); // inner s
}

Dans les versions antérieures du compilateur, lorsqu’il recherchait s dans les arguments du constructeur la déclaration « interne » de s, il trouvait la déclaration précédente (s « externe ») et le code se compilait. Depuis la version 16.10, le compilateur émet un avertissement C4700 à la place. Cela est dû au fait que le compilateur déclare désormais le s « interne » avant d’analyser les arguments du constructeur. Ainsi, la recherche s trouve le s « interne » qui n’a pas encore été initialisé.

Membre explicitement spécialisé d’un modèle de classe

Les versions antérieures du compilateur marquaient incorrectement une spécialisation explicite d’un membre de modèle de classe comme inline s’il était également défini dans le modèle principal. Ce comportement avait pour effet que le compilateur rejetait parfois du code conforme. Dans Visual Studio 2019 versions 16.10 et ultérieures, une spécialisation explicite n’est plus implicitement marquée comme inline en mode /permissive-. Examinez cet exemple :

Fichier source s.h :

// s.h
template<typename T>
struct S {
    int f() { return 1; }
};
template<> int S<int>::f() { return 2; }

Fichier source s.cpp :

// s.cpp
#include "s.h"

Fichier source main.cpp :

// main.cpp
#include "s.h"

int main()
{
}

Pour résoudre l’erreur de l’éditeur de liens dans l’exemple ci-dessus, ajoutez inline explicitement S<int>::f :

template<> inline int S<int>::f() { return 2; }

Altération de nom de type de retour déduit

Dans Visual Studio 2019 versions 16.10 et ultérieures, le compilateur modifiait la façon dont il générait des noms altérés pour les fonctions qui avaient déduit les types de retour. Par exemple, considérez les fonctions suivantes :

auto f() { return 0; }
auto g() { []{}; return 0; }

Les versions antérieures du compilateur généreraient ces noms pour l’éditeur de liens :

f: ?f@@YAHXZ -> int __cdecl f(void)
g: ?g@@YA@XZ -> __cdecl g(void)

Étonnamment, le type de retour était omis de g en raison d’un autre comportement sémantique provoqué par le lambda local dans le corps de la fonction. Cette incohérence rendait difficile l’implémentation de fonctions exportées qui avaient un type de retour déduit, car l’interface du module nécessite des informations sur la façon dont le corps d’une fonction a été compilé. Elle a besoin des informations pour produire une fonction côté importation qui peut être correctement liée à la définition.

Le compilateur omet désormais le type de retour d’une fonction de type de retour déduit. Ce comportement est cohérent avec d’autres implémentations majeures. Il existe une exception pour les modèles de fonction : cette version du compilateur introduit un nouveau comportement de nom altéré pour les modèles de fonction qui ont un type de retour déduit :

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;

Les noms altérés pour auto et decltype(auto) apparaissent désormais dans le fichier binaire, pas dans le type de retour déduit :

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)

Les versions antérieures du compilateur incluaient le type de retour déduit dans la signature. Lorsque le compilateur incluait le type de retour dans le nom altéré, il pouvait occasionner des problèmes d’éditeur de liens. Certains scénarios autrement bien formés deviendraient ambigus pour l’éditeur de liens.

Le nouveau comportement du compilateur peut produire un changement cassant de binaire. Examinez cet exemple :

Fichier source a.cpp :

// a.cpp
auto f() { return 1; }

Fichier source main.cpp :

// main.cpp
int f();
int main() { f(); }

Dans les versions antérieures à la version 16.10, le compilateur générait un nom pour auto f(), qui ressemblait à int f(), même s’il s’agissait de fonctions sémantiquement distinctes. Cela signifie que l’exemple se compilait. Pour résoudre le problème, ne vous appuyez pas sur auto dans la définition d’origine de f. Au lieu de cela, écrivez-le en tant que int f(). Étant donné que les fonctions qui ont des types de retour déduit sont toujours compilées, les implications ABI sont réduites.

Avertissement pour l’attribut nodiscard ignoré

Les versions précédentes du compilateur ignorent silencieusement certaines utilisations d’un attribut nodiscard. Elles ignoraient l’attribut s’il était dans une position syntaxique qui ne s’appliquait pas à la fonction ou à la classe déclarée. Par exemple :

static [[nodiscard]] int f() { return 1; }

Dans Visual Studio 2019 versions 16.10 et ultérieures, le compilateur émet un avertissement de niveau 4 C5240 à la place :

a.cpp(1): warning C5240: 'nodiscard': attribute is ignored in this syntactic position

Pour résoudre ce problème, déplacez l’attribut vers la position syntaxique correcte :

[[nodiscard]] static int f() { return 1; }

Avertissement pour les directives include avec des noms d’en-tête système dans le domaine du module

Dans Visual Studio 2019 versions 16.10 et ultérieures, le compilateur émet un avertissement pour empêcher une erreur courante de création d’interface de module. Si vous incluez un en-tête de bibliothèque standard après une instruction export module, le compilateur émet un avertissement C5244. Voici un exemple :

export module m;
#include <vector>

export
void f(std::vector<int>);

Le développeur ne voulait probablement pas que le module m possède le contenu de <vector>. Le compilateur émet désormais un avertissement pour vous aider à rechercher et résoudre le problème :

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

Pour résoudre ce problème, déplacez #include <vector> devant export module m; :

#include <vector>
export module m;

export
void f(std::vector<int>);

Avertissement pour les fonctions de liaison interne inutilisées

Dans Visual Studio 2019 versions 16.10 et ultérieures, le compilateur avertissait dans d’autres situations où une fonction non référencée avec liaison interne avait été supprimée. Les versions antérieures du compilateur émettaient un avertissement C4505 pour le code suivant :

static void f() // warning C4505: 'f': unreferenced function with internal linkage has been removed
{
}

Le compilateur avertit désormais à propos de fonctions auto non référencées et de fonctions non référencées dans des espaces de noms anonymes. Il émet un avertissement désactivé par défaut C5245 pour les deux fonctions suivantes :

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; };
}

Avertissement sur élision d’accolade

Dans Visual Studio 2019 versions 16.10 et ultérieures, le compilateur avertit concernant les listes d’initialisation qui n’utilisent pas d’accolades pour des sous-objets. Le compilateur émet un avertissement désactivé par défaut C5246.

Voici un exemple :

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

Pour résoudre ce problème, encapsulez l’initialisation du sous-objet dans des accolades :

S2 s2{ { 1, 2 }, 3 };

Détecter correctement si un objet const n’est pas initialisé

Dans Visual Studio 2019 versions 16.10 et ultérieures, le compilateur émet désormais l’erreur C2737 lorsque vous tentez de définir un objet const qui n’est pas entièrement initialisé :

struct S {
   int i;
   int j = 2;
};

const S s; // error C2737: 's': const object must be initialized

Les versions antérieures du compilateur autorisaient ce code à se compiler, même si S::i n’était pas initialisé.

Pour résoudre ce problème, initialisez tous les membres avant de créer une instance const d’un objet :

struct S {
   int i = 1;
   int j = 2;
};

Améliorations de la conformité dans Visual Studio 2019 version 16.11

Mode de compilateur /std:c++20

Dans Visual Studio 2019 versions 16.11 et ultérieures, le compilateur prend désormais en charge le mode de compilateur /std:c++20. Auparavant, les fonctionnalités C++20 étaient disponibles uniquement en mode /std:c++latest dans Visual Studio 2019. Les fonctionnalités C++20 qui nécessitaient initialement le mode /std:c++latest fonctionnent désormais en mode /std:c++20 ou version ultérieure dans les dernières versions de Visual Studio.

Voir aussi

Conformité du langage Microsoft C/C++