Pourquoi les instructions switch
et if
se comportent-elles différemment avec les opérateurs de conversion?
struct WrapperA
{
explicit operator bool() { return false; }
};
struct WrapperB
{
explicit operator int() { return 0; }
};
int main()
{
WrapperA wrapper_a;
if (wrapper_a) { /** this line compiles **/ }
WrapperB wrapper_b;
switch (wrapper_b) { /** this line does NOT compile **/ }
}
L'erreur de compilation est switch quantity is not an integer
alors que dans l'instruction if
elle est parfaitement reconnue en tant que bool
. (GCC)
La syntaxe est switch ( condition ) statement
avec
condition - toute expression de type intégral ou énumération, ou de type classe contextuellementimplicitement convertible en type intégral ou énumération, ou la déclaration d'une seule variable de type non-tableau de ce type avec un initialiseur de type accolade ou égal.
Tiré de cppreference.
Cela signifie que vous ne pouvez effectuer un changement de casse que sur un entier ou une énumération type. Pour que le compilateur puisse convertir implicitement Wrapper en type entier/enum, vous devez supprimer le mot clé explicite:
Le spécificateur explicite spécifie qu'un constructeur ou une fonction de conversion (depuis C++ 11) n'autorise pas les conversions implicites
Vous pouvez également convertir Wrapper en type int.
Modifier à l'adresse @ acraig5075 remarques:
Vous devez faire attention à quel opérateur est explicite et lequel est implicite. Si les deux sont implicites, le code ne sera pas compilé car il y aura une ambiguïté:
struct Wrapper
{
operator int() { return 0; }
operator bool() { return true; }
};
source_file.cpp: dans la fonction ‘int main ()’: source_file.cpp: 12: 14:
erreur: conversion de type par défaut ambiguë de ‘Wrapper’
commutateur (w) {
^ source_file.cpp: 12: 14: remarque: conversion du candidat
inclure 'Wrapper :: operator int ()' et 'Wrapper :: operator bool ()'
La seule façon de lever l'ambiguïté est de faire un casting.
Si un seul des opérateurs est explicite, l'autre sera choisi pour l'instruction switch:
#include <iostream>
struct Wrapper
{
explicit operator int() { return 0; }
operator bool() { return true; }
};
int main()
{
Wrapper w;
if (w) { /** this line compiles **/std::cout << " if is true " << std::endl; }
switch (w) {
case 0:
std::cout << "case 0" << std::endl;
break;
case 1:
std::cout << "case 1" << std::endl;
break;
}
return 0;
}
Sortie:
if is true
case 1
w
a été implicitement converti en 1
(true
) (car l'opérateur int est explicite) et la casse 1 est exécutée.
D'autre part :
struct Wrapper
{
operator int() { return 0; }
explicit operator bool() { return true; }
};
Ouput:
if is true
case 0
w
a été implicitement converti en 0
car l'opérateur bool est explicite.
Dans les deux cas, l'instruction if est vraie car w
est évalué contextuellement en un booléen à l'intérieur de l'instruction if.
Je pense ceci explique pourquoi la déclaration switch
n'est pas acceptée, alors que la déclaration if
est:
Dans les cinq contextes suivants, le type bool est attendu et la séquence de conversion implicite est construite si la déclaration
bool t(e);
est bien formée. c'est-à-dire que la fonction de conversion explicite définie par l'utilisateur, telle queexplicit T::operator bool() const;
, est considérée. On dit que cette expression e est convertible contextuellement en bool.
La déclaration d'un opérateur de conversion explicit
existe pour empêcher les conversions implicites vers ce type. C'est son but. switch
tente de convertir implicitement son argument en un entier; par conséquent, aucun opérateur explicit
ne sera appelé. C'est le comportement attendu.
Ce qui est inattendu, c'est qu'un opérateur explicit
est appelé dans le cas if
. Et raccroche ainsi une histoire.
Voir, étant donné les règles ci-dessus, la manière de rendre un type testable via if
consiste à convertir une variable non -explicit
en bool
. La chose est ... bool
est un type problématique. Il est implicitement convertible en nombres entiers. Ainsi, si vous créez un type implicitement convertible en bool
, il s'agit d'un code légal:
void foo(int);
foo(convertible_type{});
Mais c'est aussi un code sans signification. Vous n'avez jamais voulu que convertible_type
se convertisse implicitement en entier. Vous vouliez simplement le convertir en booléen à des fins de test.
Pre-C++ 11, le moyen de résoudre ce problème était le "safe bool idiom", ce qui était une douleur complexe et qui n’avait aucun sens logique (en gros, vous avez fourni une conversion implicite en un pointeur de membre, qui était convertible en un pas à des entiers ou des pointeurs réguliers).
Ainsi, en C++ 11, lorsqu'ils ont ajouté les opérateurs de conversion explicit
, ils ont créé une exception pour bool
. Si vous avez une explicit operator bool()
, ce type peut être "converti de manière contextuelle en bool
" à l'intérieur d'un nombre défini d'emplacements définis par la langue. Cela permet à explicit operator bool()
de signifier "testable dans des conditions booléennes".
switch
n'a pas besoin d'une telle protection. Si vous voulez qu'un type puisse être implicitement converti en int
à des fins switch
, rien ne s'oppose à ce qu'il ne soit pas implicitement convertible en int
à d'autres fins également.
Une réponse est que if
et switch
se comportent différemment parce que c'est ainsi que la norme a été écrite. Une autre réponse pourrait spéculer sur la raison pour laquelle la norme a été écrite de cette façon. Eh bien, je suppose que les déclarations standard if
se comportent de cette manière pour traiter un problème spécifique (les conversions implicites en bool
avaient été problématiques ), mais j'aimerais adopter une perspective différente.
Dans une instruction if
, la condition doit être une valeur booléenne. Ce n'est pas tout le monde qui pense aux déclarations if
de cette façon, probablement à cause des diverses commodités intégrées dans la langue. Cependant, à la base, une déclaration if
doit savoir "faire ceci" ou "faire cela"; "Oui ou non"; true
ou false
- c’est-à-dire une valeur booléenne. À cet égard, placer quelque chose à l'intérieur du conditionnel de l'instruction demande explicitement que le quelque chose soit converti en bool
.
D'autre part, une instruction switch
accepte tout type entier. C'est-à-dire qu'aucun type n'est préféré à tous les autres. L'utilisation de switch
peut être vue comme une demande explicite de convertir une valeur en un type intégral, mais pas nécessairement spécifiquement en int
. Il n'est donc pas jugé approprié d'utiliser une conversion en int
lorsque cette conversion spécifique doit être explicitement demandée.
Je crois que la vraie raison de ce comportement a ses racines dans C, mais avant de l'expliquer, je vais essayer de le justifier en termes C++.
Une instruction if
/while
/for
est supposée prendre tout scalaire (entier, float ou pointeur), ou instance de classe convertible en bool
. Il vérifie simplement si la valeur est équivalente à zéro. Compte tenu de cette permissivité, il est relativement inoffensif pour le compilateur d'utiliser un opérateur explicit
pour adapter une valeur à une instruction if
.
switch
, d'autre part, n'a de sens qu'avec les entiers et enum
s. Si vous essayez d'utiliser switch
avec un double
ou un pointeur, vous obtenez la même erreur. Cela facilite la définition de valeurs de cas discrètes. Etant donné qu'une instruction switch
a spécifiquement besoin de voir un entier, c'est probablement une erreur d'utiliser une instance de classe qui ne définit pas la conversion implicite.
Historiquement, la raison en est que c’est comme ça que C le fait.
C++ était à l'origine destiné à être rétro-compatible avec C (bien que cela n'ait jamais été réussi). C n'a jamais eu de type booléen jusqu'à une époque plus récente. Les déclarations de if
de C devaient être permissives, car il n'y avait tout simplement pas d'autre moyen de le faire. Bien que C ne fasse pas de conversions de type structure-scalaire comme le fait C++, le traitement des méthodes explicit
par C++ reflète le fait que les instructions if
sont très permissives dans les deux langages.
L'instruction switch
de C, contrairement à if
, doit fonctionner avec des valeurs discrètes, elle ne peut donc pas être aussi permissive.
Il y a deux problèmes avec votre code . Premièrement, le ou les opérateurs de conversion ne doivent pas être explicites pour travailler avec les instructions switch
. Deuxièmement, la condition switch
requiert un type entier et les deux int
et bool
sont de tels types est une ambiguïté . Si vous changez votre classe pour qu’elle ne présente pas ces deux problèmes, la switch
compilera et fonctionnera comme prévu.
Une autre solution qui ne nécessite pas de changer de classe consiste à convertir explicitement (static_cast
fera) la valeur en int
ou bool
.