/Zc:ternary
(Applicare le regole dell'operatore condizionale)
Abilitare l'applicazione delle regole standard C++ per i tipi e la qualificazione const o volatile (cv) del secondo e terzo operando in un'espressione di operatore condizionale.
Sintassi
/Zc:ternary
[-
]
Osservazioni:
A partire da Visual Studio 2017, il compilatore supporta il comportamento dell'operatore condizionale standard (?:
) C++. È noto anche come operatore ternario. Lo standard C++ richiede che gli operandi ternari soddisfino una di tre condizioni: gli operandi devono essere dello stesso tipo e const
o volatile
qualifica (cv-qualification) oppure un solo operando deve essere convertibile in modo non ambiguo nello stesso tipo e qualifica cv dell'altro. In alternativa, uno o entrambi gli operandi devono essere un'espressione throw. Nelle versioni precedenti a Visual Studio 2017 versione 15.5, il compilatore ha consentito conversioni considerate ambigue dallo standard.
Quando si specifica l'opzione /Zc:ternary
, il compilatore è conforme allo standard. Rifiuta il codice che non soddisfa le regole per i tipi corrispondenti e la qualificazione cv del secondo e del terzo operando.
L'opzione /Zc:ternary
è disattivata per impostazione predefinita in Visual Studio 2017. Usare /Zc:ternary
per abilitare il comportamento conforme o /Zc:ternary-
specificare in modo esplicito il comportamento precedente del compilatore non conforme. L'opzione /permissive-
abilita in modo implicito questa opzione, ma può essere sottoposta a override tramite /Zc:ternary-
.
Esempi
In questo esempio viene illustrato come una classe che fornisce sia l'inizializzazione non esplicita da un tipo che la conversione a un tipo può causare conversioni ambigue. Questo codice viene accettato dal compilatore per impostazione predefinita, ma rifiutato quando /Zc:ternary
viene specificato o /permissive-
.
// 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;
}
Per correggere questo codice, impostare un cast esplicito sul tipo comune preferito o impedire una direzione di conversione del tipo. È possibile evitare che il compilatore corrisponda a una conversione di tipo rendendo esplicita la conversione.
Un'eccezione importante a questo modello comune è quando il tipo degli operandi è uno dei tipi di stringa con terminazione Null, ad esempio const char*
, const char16_t*
e così via. È anche possibile riprodurre l'effetto con i tipi di matrice e i tipi di puntatore a cui decadono. Il comportamento quando il secondo o il terzo operando effettivo in ?:
è un valore letterale stringa del tipo corrispondente dipende dallo standard linguistico usato. C++17 ha modificato la semantica per questo caso da C++14. Di conseguenza, il compilatore accetta il codice nell'esempio seguente sotto l'impostazione predefinita /std:c++14
, ma lo rifiuta quando si specifica /std:c++17
o versioni successive.
// 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
}
Per correggere questo codice, eseguire il cast di uno degli operandi in modo esplicito.
In /Zc:ternary
il compilatore rifiuta gli operatori condizionali in cui uno degli argomenti è di tipo void
e l'altro non è un'espressione throw
. Un uso comune di questo modello è nelle macro simili a 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 soluzione tipica consiste nel sostituire l'argomento non void con void()
.
Questo esempio mostra il codice che genera un errore sia /Zc:ternary
in che /Zc:ternary-
in :
// 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
}
Questo codice in precedenza ha restituito questo errore:
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
Con /Zc:ternary
, il motivo dell'errore diventa più chiaro. È possibile usare qualsiasi convenzione di chiamata definita dall'implementazione per generare ogni espressione lambda. Tuttavia, il compilatore non ha alcuna regola di preferenza per disambiguare le possibili firme lambda. Il nuovo output è simile al seguente:
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>)'
Una fonte comune di problemi rilevati da deriva dagli /Zc:ternary
operatori condizionali usati nella meta-programmazione del modello. Alcuni tipi di risultati cambiano in questa opzione. L'esempio seguente illustra due casi in cui /Zc:ternary
cambia il tipo di risultato di un'espressione condizionale in un contesto di programmazione non meta-programmazione:
// 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;
}
La correzione tipica consiste nell'applicare un std::remove_reference
tratto al tipo di risultato, se necessario per mantenere il comportamento precedente.
Per altre informazioni sui problemi di conformità in Visual C++, vedere Nonstandard Behavior.
Per impostare l'opzione del compilatore nell'ambiente di sviluppo di Visual Studio
Aprire la finestra di dialogo Pagine delle proprietà del progetto. Per informazioni dettagliate, vedere Impostare il compilatore e le proprietà di compilazione.
Selezionare la pagina delle proprietà Proprietà di configurazione>C/C++>Riga di comando.
Modificare la proprietà Opzioni aggiuntive per includere
/Zc:ternary
o/Zc:ternary-
quindi scegliere OK.