55 votes

Façon compacte d'écrire une instruction if (..) avec plusieurs égalités

Est-il une meilleure façon d'écrire du code comme ceci:

if (var == "first case" or var == "second case" or var == "third case" or ...)

En Python, je peux écrire:

if var in ("first case", "second case", "third case", ...)

ce qui me donne aussi l'occasion de passer facilement à la liste de bonnes options:

good_values = "first case", "second case", "third case"
if var in good_values

C'est juste un exemple: le type d' var peut être différent à partir d'une chaîne, mais je suis seulement intéressé dans la variante (or) comparaisons (==). var peut être non-const, tandis que la liste des options est connu au moment de la compilation.

Pro bonus:

  • la paresse de l' or
  • le temps de compilation déroulement de la boucle
  • facile d'étendre à d'autres opérateurs que l' ==

44voto

Slava Points 399

si vous voulez augmenter le temps de compilation, vous pouvez utiliser quelque chose comme ceci

 template<class T1, class T2>
bool isin(T1&& t1, T2&& t2) {
   return t1 == t2;
}

template<class T1, class T2, class... Ts>
bool isin(T1&& t1 , T2&& t2, T2&&... ts) {
   return t1 == t2 || isin(t1, ts...);
}

std::string my_var = ...; // somewhere in the code
...
bool b = isin(my_var, "fun", "gun", "hun");
 

Je ne l'ai pas testé en fait, et l'idée vient du discours 'Les modèles Variadic sont funadic' d'Alexandrescu. Donc, pour les détails (et la bonne mise en œuvre), observez cela.

Edit: en c ++ 17 ils ont introduit une belle syntaxe d’ expression de pli

 template<typename... Args>
bool all(Args... args) { return (... && args); }

bool b = all(true, true, true, false);
 // within all(), the unary left fold expands as
 //  return ((true && true) && true) && false;
 // b is false
 

27voto

Kerrek SB Points 194696

L'algorithme any_of pourrait fonctionner raisonnablement bien ici:

 #include <algorithm>
#include <initializer_list>

auto tokens = { "abc", "def", "ghi" };

bool b = std::any_of(tokens.begin(), tokens.end(),
                     [&var](const char * s) { return s == var; });
 

(Vous souhaiterez peut-être limiter la portée de tokens au contexte minimal requis.)

Ou vous créez un modèle de wrapper:

 #include <algorithm>
#include <initializer_list>
#include <utility>

template <typename T, typename F>
bool any_of_c(const std::initializer_list<T> & il, F && f)
{
    return std::any_of(il.begin(), il.end(), std::forward<F>(f));
}
 

Usage:

 bool b = any_of_c({"abc", "def", "ghi"},
                  [&var](const char * s) { return s == var; });
 

11voto

Dúthomhas Points 1163

Alrighty, alors, vous voulez Radicale de la Langue de Modification. Plus précisément, vous voulez créer votre propre opérateur. Est-il prêt?

La syntaxe

Je vais modifier la syntaxe à utiliser un C et C++-style de liste:

if (x in {x0, ...}) ...

En outre, nous allons laisser nos nouvelles en exploitant s'appliquer à n'importe quel conteneur pour qui begin() et end() sont définies:

if (x in my_vector) ...

Il y a une mise en garde: ce n'est pas un vrai opérateur et donc il doit toujours être mis entre parenthèses car il est propre expression:

bool ok = (x in my_array);

my_function( (x in some_sequence) );

Le code

La première chose à prendre en compte est que RLM nécessite souvent une macro et l'opérateur de l'abus. Heureusement, pour une simple adhésion de prédicat, l'abus est en fait pas si mal que ça.

#ifndef DUTHOMHAS_IN_OPERATOR_HPP
#define DUTHOMHAS_IN_OPERATOR_HPP

#include <algorithm>
#include <initializer_list>
#include <iterator>
#include <type_traits>
#include <vector>

//----------------------------------------------------------------------------
// The 'in' operator is magically defined to operate on any container you give it
#define in , in_container() =

//----------------------------------------------------------------------------
// The reverse-argument membership predicate is defined as the lowest-precedence 
// operator available. And conveniently, it will not likely collide with anything.
template <typename T, typename Container>
typename std::enable_if <!std::is_same <Container, T> ::value, bool> ::type
operator , ( const T& x, const Container& xs )
{
  using std::begin;
  using std::end;
  return std::find( begin(xs), end(xs), x ) != end(xs);
}

template <typename T, typename Container>
typename std::enable_if <std::is_same <Container, T> ::value, bool> ::type
operator , ( const T& x, const Container& y )
{
  return x == y;
}

//----------------------------------------------------------------------------
// This thunk is used to accept any type of container without need for 
// special syntax when used.
struct in_container
{
  template <typename Container>
  const Container& operator = ( const Container& container )
  {
    return container;
  }

  template <typename T>
  std::vector <T> operator = ( std::initializer_list <T> xs )
  {
    return std::vector <T> ( xs );
  }
};

#endif

L'utilisation de la

Super! Maintenant, nous pouvons l'utiliser dans toutes les manières que vous attendez une à l'opérateur d'être utile. Selon votre domaine d'intérêt particulier, voir exemple 3:

#include <iostream>
#include <set>
#include <string>
using namespace std;

void f( const string& s, const vector <string> & ss ) { cout << "nope\n\n"; }
void f( bool b ) { cout << "fooey!\n\n"; }

int main()
{
  cout << 
    "I understand three primes by digit or by name.\n"
    "Type \"q\" to \"quit\".\n\n";

  while (true)
  {
    string s;
    cout << "s? ";
    getline( cin, s );

    // Example 1: arrays 
    const char* quits[] = { "quit", "q" };
    if (s in quits) 
      break;

    // Example 2: vectors
    vector <string> digits { "2", "3", "5" };
    if (s in digits)
    {
      cout << "a prime digit\n\n";
      continue;
    }

    // Example 3: literals
    if (s in {"two", "three", "five"})
    {
      cout << "a prime name!\n\n";
      continue;
    }

    // Example 4: sets
    set <const char*> favorites{ "7", "seven" };
    if (s in favorites)
    {
      cout << "a favorite prime!\n\n";
      continue;
    }

    // Example 5: sets, part deux
    if (s in set <string> { "TWO", "THREE", "FIVE", "SEVEN" })
    {
      cout << "(ouch! don't shout!)\n\n";
      continue;
    }

    // Example 6: operator weirdness
    if (s[0] in string("014") + "689")
    {
      cout << "not prime\n\n";
      continue;
    }

    // Example 7: argument lists unaffected    
    f( s, digits );
  }
  cout << "bye\n";
}

Améliorations possibles

Il y a toujours des choses qui peut être fait pour améliorer le code à vos besoins. Vous pouvez ajouter un ni (non -) opérateur (Ajouter un thunk de nouveau type de conteneur). Vous pouvez envelopper le thunk conteneurs dans un espace de noms (une bonne idée). Vous pouvez vous spécialiser sur des choses comme l' std::set utilisation de l' .count() fonction de membre au lieu de O(n) de recherche. Etc.

Vos autres préoccupations

  • const vs mutable : pas un problème; les deux sont utilisables avec l'opérateur
  • la paresse de l' or : Techniquement, or est pas paresseux, il est court-circuité. L' std::find() algorithme également des courts-circuits, de la même façon.
  • le temps de compilation déroulement de la boucle : pas vraiment applicable ici. Votre code d'origine n'a pas utiliser de boucles; tandis que, std::find() n', toute déroulement de la boucle qui peut se produire est le compilateur.
  • facile d'étendre à d'autres opérateurs que == : C'est effectivement une question distincte, vous n'êtes plus à la recherche à une simple adhésion de prédicat, mais sont en train d'examiner une fonctionnelle fois-filtre. Il est tout à fait possible de créer un algorithme qui le fait, mais la Bibliothèque Standard fournit l' any_of() de la fonction, ce qui est exactement ce que fait. (C'est juste pas aussi jolie que notre RLM 'dans' l'opérateur. Cela dit, tout programmeur C++ de se comprendre facilement. Ces réponses ont déjà été proposées ici.)

Espérons que cette aide.

9voto

Let Us Embed Points 999

Tout d'abord, je recommande d'utiliser une boucle for , qui est à la fois la solution la plus simple et la plus lisible:

 for (i = 0; i < n; i++) {
   if (var == eq[i]) {
      // if true
      break;
   }
}
 

Cependant, certaines autres méthodes sont également disponibles, par exemple std::all_of , std::any_of , std::none_of (en #include <algorithm> ).

Regardons le programme d'exemple simple qui contient tous les mots-clés ci-dessus

 #include <vector>
#include <numeric>
#include <algorithm>
#include <iterator>
#include <iostream>
#include <functional>

int main()
{
    std::vector<int> v(10, 2);
    std::partial_sum(v.cbegin(), v.cend(), v.begin());
    std::cout << "Among the numbers: ";
    std::copy(v.cbegin(), v.cend(), std::ostream_iterator<int>(std::cout, " "));
    std::cout << '\\n';

    if (std::all_of(v.cbegin(), v.cend(), [](int i){ return i % 2 == 0; })) 
    {
        std::cout << "All numbers are even\\n";
    }
    if (std::none_of(v.cbegin(), v.cend(), std::bind(std::modulus<int>(),
                                  std::placeholders::_1, 2))) 
    {
        std::cout << "None of them are odd\\n";
    }
    struct DivisibleBy
    {
        const int d;
        DivisibleBy(int n) : d(n) {}
        bool operator()(int n) const { return n % d == 0; }
    };

    if (std::any_of(v.cbegin(), v.cend(), DivisibleBy(7))) 
    {
        std::cout << "At least one number is divisible by 7\\n";
    }
}
 

6voto

ivaigult Points 401

Vous pouvez utiliser std::set de tester si var appartient. (Compiler du c++11 activé)

#include <iostream>
#include <set>

int main()
{
    std::string el = "abc";

    if (std::set<std::string>({"abc", "def", "ghi"}).count(el))
        std::cout << "abc belongs to {\"abc\", \"def\", \"ghi\"}" << std::endl;

    return 0;
}

L'avantage est qu' std::set<std::string>::count travaux en O(log(n)) du temps (où est - n est le nombre de chaînes de test) en comparaison des non compacte if de la sorcière est - O(n) en général. L'inconvénient est que la construction de l'ensemble prend O(n*log(n)). Donc, construire une fois, comme:

static std::set<std::string> the_set = {"abc", "def", "ghi"};

Mais, de l'OMI, il serait préférable de laisser la condition est, sauf si elle contient plus de 10 chaînes de vérifier. Les avantages de l'utilisation de std::set à ce type de test s'affiche uniquement pour les grosses n. Aussi, simple non compacte if est plus facile à lire pour une moyenne de développeur c++.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X