/Zc:ternary
(Appliquer des règles d’opérateur conditionnel)
Activez l’application des règles standard C++ pour les types et la qualification const ou volatile (cv) des deuxième et troisième opérandes dans une expression d’opérateur conditionnel.
Syntaxe
/Zc:ternary
[-
]
Notes
À compter de Visual Studio 2017, le compilateur prend en charge le comportement de?:
l’opérateur conditionnel standard C++. Il est également appelé opérateur ternaire. La norme C++ exige que les opérandes ternaires remplissent l’une des trois conditions suivantes : les opérandes doivent être de même type et const
ou volatile
qualification (cv-qualification), ou un seul opérande doit être non ambiguëment convertible en même type et cv-qualification que l’autre. Ou bien, un ou les deux opérandes doivent être une expression levée. Dans les versions antérieures à Visual Studio 2017 version 15.5, le compilateur a autorisé les conversions considérées comme ambiguës par la norme.
Lorsque l’option /Zc:ternary
est spécifiée, le compilateur est conforme à la norme. Il rejette le code qui ne répond pas aux règles relatives aux types correspondants et à la qualification cv des deuxième et troisième opérandes.
L’option /Zc:ternary
est désactivée par défaut dans Visual Studio 2017. Permet /Zc:ternary
d’activer le comportement conforme ou /Zc:ternary-
de spécifier explicitement le comportement du compilateur non conforme précédent. L’option /permissive-
active implicitement cette option, mais elle peut être substituée à l’aide /Zc:ternary-
de .
Exemples
Cet exemple montre comment une classe qui fournit à la fois une initialisation non explicite d’un type et une conversion vers un type, peut entraîner des conversions ambiguës. Ce code est accepté par le compilateur par défaut, mais rejeté quand /Zc:ternary
ou /permissive-
est spécifié.
// zcternary1.cpp
// Compile by using: cl /EHsc /W4 /nologo /Zc:ternary zcternary1.cpp
struct A
{
long l;
A(int i) : l{i} {} // explicit prevents conversion of int
operator int() const { return static_cast<int>(l); }
};
int main()
{
A a(42);
// Accepted when /Zc:ternary (or /permissive-) is not used
auto x = true ? 7 : a; // old behavior prefers A(7) over (int)a
auto y = true ? A(7) : a; // always accepted
auto z = true ? 7 : (int)a; // always accepted
return x + y + z;
}
Pour corriger ce code, effectuez un cast explicite vers le type commun préféré ou empêchez une direction de conversion de type. Vous pouvez empêcher le compilateur de correspondre à une conversion de type en rendant la conversion explicite.
Une exception importante à ce modèle commun est lorsque le type des opérandes est l’un des types de chaînes terminées par null, tels que const char*
, const char16_t*
et ainsi de suite. Vous pouvez également reproduire l’effet avec les types de tableaux et les types de pointeurs vers tableaux auxquels ils se décomposent. Le comportement lorsque le deuxième ou le troisième opérande réel à utiliser ?:
est un littéral de chaîne de type correspondant dépend de la norme de langage utilisée. C++17 a changé de sémantique pour ce cas de C++14. Par conséquent, le compilateur accepte le code dans l’exemple suivant sous la valeur par défaut /std:c++14
, mais le rejette lorsque vous spécifiez /std:c++17
ou version ultérieure.
// zcternary2.cpp
// Compile by using: cl /EHsc /W4 /nologo /Zc:ternary /std:c++17 zcternary2.cpp
struct MyString
{
const char * p;
MyString(const char* s = "") noexcept : p{s} {} // from char*
operator const char*() const noexcept { return p; } // to char*
};
int main()
{
MyString s;
auto x = true ? "A" : s; // MyString: permissive prefers MyString("A") over (const char*)s
}
Pour corriger ce code, effectuez un cast explicite de l’un des opérandes.
Sous /Zc:ternary
, le compilateur rejette les opérateurs conditionnels où l’un des arguments est de type void
et l’autre n’est pas une throw
expression. Un usage courant de ce modèle est dans les macros de type ASSERT :
// zcternary3.cpp
// Compile by using: cl /EHsc /W4 /nologo /Zc:ternary /c zcternary3.cpp
void myassert(const char* text, const char* file, int line);
#define ASSERT(ex) (void)((ex) ? 0 : myassert(#ex, __FILE__, __LINE__))
// To fix, define it this way instead:
// #define ASSERT(ex) (void)((ex) ? void() : myassert(#ex, __FILE__, __LINE__))
int main()
{
ASSERT(false); // C3447
}
La solution classique consiste à remplacer l’argument non-void par void()
.
Cet exemple montre le code qui génère une erreur sous les deux /Zc:ternary
et /Zc:ternary-
:
// zcternary4.cpp
// Compile by using:
// cl /EHsc /W4 /nologo /Zc:ternary zcternary4.cpp
// cl /EHsc /W4 /nologo /Zc:ternary zcternary4.cpp
int main() {
auto p1 = [](int a, int b) { return a > b; };
auto p2 = [](int a, int b) { return a > b; };
auto p3 = true ? p1 : p2; // C2593 under /Zc:ternary, was C2446
}
Ce code a précédemment donné cette erreur :
error C2446: ':': no conversion from 'foo::<lambda_f6cd18702c42f6cd636bfee362b37033>' to 'foo::<lambda_717fca3fc65510deea10bc47e2b06be4>'
note: No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called
Avec /Zc:ternary
, la raison de l’échec devient plus claire. L’une des conventions d’appel définies par l’implémentation peut être utilisée pour générer chaque lambda. Toutefois, le compilateur n’a aucune règle de préférence pour lever l’ambiguïté des signatures lambda possibles. La nouvelle sortie ressemble à ceci :
error C2593: 'operator ?' is ambiguous
note: could be 'built-in C++ operator?(bool (__cdecl *)(int,int), bool (__cdecl *)(int,int))'
note: or 'built-in C++ operator?(bool (__stdcall *)(int,int), bool (__stdcall *)(int,int))'
note: or 'built-in C++ operator?(bool (__fastcall *)(int,int), bool (__fastcall *)(int,int))'
note: or 'built-in C++ operator?(bool (__vectorcall *)(int,int), bool (__vectorcall *)(int,int))'
note: while trying to match the argument list '(foo::<lambda_717fca3fc65510deea10bc47e2b06be4>, foo::<lambda_f6cd18702c42f6cd636bfee362b37033>)'
Une source courante de problèmes détectés provient /Zc:ternary
d’opérateurs conditionnels utilisés dans la méta-programmation de modèle. Certains types de résultats changent sous ce commutateur. L’exemple suivant illustre deux cas où /Zc:ternary
change le type de résultat d’une expression conditionnelle dans un contexte de non-méta-programmation :
// zcternary5.cpp
// Compile by using: cl /EHsc /W4 /nologo /Zc:ternary zcternary5.cpp
int main(int argc, char**) {
char a = 'A';
const char b = 'B';
decltype(auto) x = true ? a : b; // char without, const char& with /Zc:ternary
const char(&z)[2] = argc > 3 ? "A" : "B"; // const char* without /Zc:ternary
return x > *z;
}
Le correctif classique consiste à appliquer une std::remove_reference
caractéristique sur le type de résultat, si nécessaire pour conserver l’ancien comportement.
Pour plus d’informations sur les problèmes de conformité dans Visual C++, consultez Nonstandard Behavior.
Pour définir cette option du compilateur dans l'environnement de développement Visual Studio
Ouvrez la boîte de dialogue Pages de propriété du projet. Pour plus d’informations, consultez Définir le compilateur C++ et les propriétés de build dans Visual Studio.
Sélectionnez la page de propriétés Propriétés de configuration>C/C++>Ligne de commande.
Modifiez la propriété Options supplémentaires pour inclure
/Zc:ternary
ou/Zc:ternary-
choisir OK.