web-dev-qa-db-fra.com

Pourquoi commuter et si les instructions se comportent différemment avec les opérateurs de conversion?

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)

15
nyarlathotep108

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.

17
Clonk

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 que explicit T::operator bool() const;, est considérée. On dit que cette expression e est convertible contextuellement en bool.

  • contrôler l'expression de if, while, for;
  • les opérateurs logiques!, && et ||;
  • l'opérateur conditionnel?:;
  • static_assert;
  • noexcept.
10
Marco Luzzara

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.

3
Nicol Bolas

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 boolavaient é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.

3
JaMiT

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

0
luther

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.

0
navyblue