Existe-t-il une meilleure façon d'écrire du code comme celui-ci:
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 également la possibilité de passer facilement la liste des bonnes options:
good_values = "first case", "second case", "third case"
if var in good_values
Ceci est juste un exemple: le type de var
peut être différent d'une chaîne, mais je ne suis intéressé que par des comparaisons alternatives (or
) (==
). var
peut être non -const
, tandis que la liste des options est connue au moment de la compilation.
Bonus Pro:
or
==
si vous voulez étendre le temps de compilation, vous pouvez utiliser quelque chose comme ça
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 d'Alexandrescu "Les modèles Variadic sont funadiques". Donc, pour les détails (et la bonne mise en œuvre), regardez cela.
Edit: en c ++ 17 ils ont introduit une syntaxe Nice expression de repli
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
Le any_of
l'algorithme pourrait fonctionner assez 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 pouvez 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; });
Très bien alors, vous voulez Modification du langage radical. Plus précisément, vous souhaitez créer votre propre opérateur. Prêt?
Syntaxe
Je vais modifier la syntaxe pour utiliser une liste de style C et C++:
if (x in {x0, ...}) ...
De plus, nous laisserons notre nouvel opérateur in s'appliquer à tout conteneur pour lequel begin()
et end()
sont définis:
if (x in my_vector) ...
Il y a une mise en garde: ce n'est pas un véritable opérateur et il doit donc toujours être entre parenthèses comme sa propre expression:
bool ok = (x in my_array);
my_function( (x in some_sequence) );
Le code
La première chose à savoir est que RLM nécessite souvent des abus de macro et d'opérateur. Heureusement, pour un simple prédicat d'adhésion, l'abus n'est en fait pas si mal.
#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
tilisation
Génial! Maintenant, nous pouvons l'utiliser dans tous les façons dont vous vous attendriez à ce qu'un opérateur in soit utile. Selon votre intérêt particulier, voir l'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 potentielles
Il y a toujours des choses à faire pour améliorer le code pour vos besoins spécifiques. Vous pouvez ajouter un opérateur ni (non-in) (Ajouter un nouveau type de conteneur thunk). Vous pouvez envelopper les conteneurs thunk dans un espace de noms (une bonne idée). Vous pouvez vous spécialiser sur des choses comme std::set
Pour utiliser la fonction membre .count()
au lieu de la recherche O(n). Etc.
Vos autres préoccupations
const
vs mutable
: pas de problème; les deux sont utilisables avec l'opérateuror
: Techniquement, or
n'est pas pas paresseux, il est court-circuité. L'algorithme std::find()
court-circuite également de la même manière.std::find()
le fait, tout déroulement de boucle qui peut se produire appartient au compilateur.==
: C'est en fait un problème distinct; vous ne regardez plus un simple prédicat d'appartenance, mais envisagez maintenant un filtre de pliage fonctionnel. Il est tout à fait possible de créer un algorithme qui fait cela, mais la bibliothèque standard fournit la fonction any_of()
, qui fait exactement cela. (Ce n'est tout simplement pas aussi joli que notre opérateur RLM 'in'. Cela dit, tout programmeur C++ le comprendra facilement. De telles réponses ont déjà été proposées ici.)J'espère que cela t'aides.
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, d'autres méthodes sont également disponibles, par exemple std::all_of
, std::any_of
, std::none_of
(dans #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";
}
}
Vous pouvez utiliser std :: set pour tester si var lui appartient. (Compilation avec 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 que std::set<std::string>::count
Fonctionne en O(log(n))
temps (où est n
est le nombre de chaînes à tester) comparé à non compact if
qui est O(n)
en général. L'inconvénient est que la construction de l'ensemble prend O(n*log(n))
. Donc, construisez-le une fois, comme:
static std::set<std::string> the_set = {"abc", "def", "ghi"};
Mais, OMI, il serait préférable de laisser la condition telle qu'elle est, à moins qu'elle ne contienne plus de 10 chaînes à vérifier. Les avantages de performances de l'utilisation de std :: set pour un tel test n'apparaissent que pour les gros n
. De plus, un simple if
simple non compact est plus facile à lire pour un développeur c ++ moyen.
La chose la plus proche serait quelque chose comme:
template <class K, class U, class = decltype(std::declval<K>() == std::declval<U>())>
bool in(K&& key, std::initializer_list<U> vals)
{
return std::find(vals.begin(), vals.end(), key) != vals.end();
}
Nous devons prendre un argument de type initializer_list<U>
pour que nous puissions passer une braced-init-list comme {a,b,c}
. Cela copie les éléments, mais nous allons probablement le faire parce que nous fournissons des littéraux donc ce n'est probablement pas un gros problème.
Nous pouvons l'utiliser comme ceci:
std::string var = "hi";
bool b = in(var, {"abc", "def", "ghi", "hi"});
std::cout << b << std::endl; // true
Si vous avez accès à C++ 14 (vous ne savez pas si cela fonctionne avec C++ 11), vous pouvez écrire quelque chose comme ceci:
template <typename T, typename L = std::initializer_list<T>>
constexpr bool is_one_of(const T& value, const L& list)
{
return std::any_of(std::begin(list), std::end(list), [&value](const T& element) { return element == value; });
};
Un appel ressemblerait à ceci:
std::string test_case = ...;
if (is_one_of<std::string>(test_case, { "first case", "second case", "third case" })) {...}
ou comme ça
std::string test_case = ...;
std::vector<std::string> allowedCases{ "first case", "second case", "third case" };
if (is_one_of<std::string>(test_case, allowedCases)) {...}
Si vous n'aimez pas "encapsuler" les cas autorisés dans un type de liste, vous pouvez également écrire une petite fonction d'aide comme celle-ci:
template <typename T, typename...L>
constexpr bool is_one_of(const T& value, const T& first, const L&... next) //First is used to be distinct
{
return is_one_of(value, std::initializer_list<T>{first, next...});
};
Cela vous permettra de l'appeler comme ceci:
std::string test_case = ...;
if (is_one_of<std::string>(test_case, "first case", "second case", "third case" )) {...}
Il convient de noter que dans la plupart des Java et code C++ que j'ai vus, répertorier environ 3 conditions) est la pratique acceptée. C'est certainement plus lisible que les solutions "intelligentes". Si cela se produit si souvent, c'est un frein majeur, c'est une odeur de conception de toute façon et une approche basée sur des modèles ou polymorphe aiderait probablement à éviter cela.
Donc ma réponse est l'opération "nulle". Continuez à faire la chose la plus verbeuse, c'est le plus accepté.
Vous pouvez utiliser un boîtier de commutation. Au lieu d'avoir une liste de cas distincts, vous pourriez avoir:
using namespace std;
int main () {char grade = 'B';
switch(grade)
{
case 'A' :
case 'B' :
case 'C' :
cout << "Well done" << endl;
break;
case 'D' :
cout << "You passed" << endl;
break;
case 'F' :
cout << "Better try again" << endl;
break;
default :
cout << "Invalid grade" << endl;
}
cout << "Your grade is " << grade << endl;
return 0;
}
Ainsi, vous pouvez regrouper vos résultats: A, B et C sortiront "bien fait". J'ai pris cet exemple de Tutorials Point: http://www.tutorialspoint.com/cplusplus/cpp_switch_statement.htm