Подтвердить что ты не робот

Компактный способ записи if (..) с множеством равенств

Есть ли лучший способ написать такой код:

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

В Python я могу написать:

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

который также дает мне возможность легко передать список хороших вариантов:

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

Это просто пример: тип var может отличаться от строки, но меня интересуют только альтернативные (or) сравнения (==). var может быть не const, а список параметров известен во время компиляции.

Про бонус:

  • лень or
  • разворот цикла компиляции
  • легко распространяется на другие операторы, чем ==
4b9b3361

Ответ 1

если вы хотите развернуть это время компиляции, вы можете использовать что-то вроде этого

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");

Я не тестировал это на самом деле, и идея исходит от Alexandrescu. Вариадические шаблоны - это забавные разговоры. Поэтому для деталей (и правильной реализации) смотрите это.

Изменить: в С++ 17 они внесли приятный fold выражение синтаксис

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

Ответ 2

Алгоритм any_of может работать достаточно хорошо:

#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; });

(Вы можете ограничить область tokens минимально необходимым контекстом.)

Или вы создаете шаблон оболочки:

#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));
}

Использование:

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

Ответ 3

Итак, вы хотите Радикальное изменение языка. В частности, вы хотите создать своего собственного оператора. Готов?

Синтаксис

Я хочу изменить синтаксис, чтобы использовать список в стиле C и С++:

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

Кроме того, мы добавим наш новый оператор в в любой контейнер, для которого определены begin() и end():

if (x in my_vector) ...

Существует одно предостережение: это не истинный оператор, поэтому он всегда должен быть заключен в скобки как его собственное выражение:

bool ok = (x in my_array);

my_function( (x in some_sequence) );

Код

Первое, что нужно знать, это то, что RLM часто требует злоупотребления макросами и операторами. К счастью, для простого предиката для членов, злоупотребление на самом деле не так уж плохо.

#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

Использование

Отлично! Теперь мы можем использовать его всеми возможными способами, чтобы вы могли использовать оператор в. В соответствии с вашим особым интересом см. Пример 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";
}

Потенциальные улучшения

Всегда есть вещи, которые можно сделать для улучшения кода для ваших конкретных целей. Вы можете добавить оператор ni (not-in) (добавить новый тип контейнера thunk). Вы можете обернуть контейнеры thunk в пространство имен (хорошая идея). Вы можете специализироваться на таких вещах, как std::set, чтобы использовать функцию члена .count() вместо поиска O (n). Etc.

Другие проблемы

  • const vs mutable: не проблема; оба могут использоваться с оператором
  • лень or: Технически, or не ленив, он закорочен. Алгоритм std::find() также замыкается так же.
  • разворот цикла компиляции: здесь не применимо. В исходном коде не использовались циклы; в то время как std::find() делает, любая развертка цикла, которая может произойти, зависит от компилятора.
  • легко распространяться на операторы, отличные от ==: это действительно отдельная проблема; вы больше не смотрите на простой предикат членства, но теперь рассматриваете функциональный сбрасывающий фильтр. Вполне возможно создать алгоритм, который это делает, но стандартная библиотека предоставляет функцию any_of(), которая делает именно это. (Это просто не так красиво, как наш RLM "в" операторе. Тем не менее, любой программист на C++ легко поймет это. Такие ответы уже были предложены здесь.)

Надеюсь, что это поможет.

Ответ 4

Во-первых, я рекомендую использовать цикл for, который является самым простым и наиболее читаемое решение:

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

Однако доступны и другие методы, например std::all_of, std::any_of, std::none_of#include <algorithm>).

Посмотрим на простую примерную программу, содержащую все вышеперечисленные ключевые слова

#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";
    }
}

Ответ 5

Вы можете использовать std:: set, чтобы проверить, принадлежит ли ему var. (Скомпилировать с включенным С++ 11)

#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;
}

Преимущество заключается в том, что std::set<std::string>::count работает в O(log(n)) времени (где n - количество строк для тестирования) по сравнению с некомпактным if witch O(n) в целом. Недостатком является то, что построение множества принимает O(n*log(n)). Итак, постройте его один раз, например:

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

Но, ИМО, было бы лучше оставить условие как есть, если оно содержит более 10 строк для проверки. Преимущества производительности при использовании std:: set для такого теста отображаются только для больших n. Кроме того, простой некомпактный if легче читать для среднего разработчика С++.

Ответ 6

Ближайшая вещь будет примерно такой:

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();
}

Нам нужно принять аргумент типа initializer_list<U>, чтобы мы могли перейти в список с расширенным набором команд, например {a,b,c}. Это копирует элементы, но, по-видимому, мы это делаем, потому что мы предоставляем литералы, поэтому, вероятно, это не большая проблема.

Мы можем использовать это так:

std::string var = "hi";    
bool b = in(var, {"abc", "def", "ghi", "hi"});
std::cout << b << std::endl; // true

Ответ 7

Если у вас есть доступ к С++ 14 (не уверен, что это работает с С++ 11), вы можете написать что-то вроде этого:

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; });
};

Вызов будет выглядеть так:

std::string test_case = ...;
if (is_one_of<std::string>(test_case, { "first case", "second case", "third case" })) {...}

или как это

std::string test_case = ...;
std::vector<std::string> allowedCases{ "first case", "second case", "third case" };
if (is_one_of<std::string>(test_case, allowedCases)) {...}

Если вам не нравится "обертывать" разрешенные случаи в тип списка, вы также можете написать небольшую вспомогательную функцию следующим образом:

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...});
};

Это позволит вам называть его следующим образом:

std::string test_case = ...;
if (is_one_of<std::string>(test_case, "first case", "second case", "third case" )) {...}

Полный пример на Coliru

Ответ 8

Стоит отметить, что в большинстве кодов Java и С++, которые я видел, перечисление 3 или около того условностей является принятой практикой. Это, безусловно, более читаемо, чем "умные" решения. Если это случается так часто, это серьезное сопротивление, что запах дизайна в любом случае и шаблонный или полиморфный подход, вероятно, помогут избежать этого.

Итак, мой ответ - это "нулевая" операция. Просто продолжайте делать более многословную вещь, она наиболее приемлема.

Ответ 9

Вы можете использовать корпус коммутатора. Вместо списка отдельных случаев вы можете:

включить

с использованием пространства имен 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;

}

Итак, вы можете группировать результаты вместе: A, B и C выведут "хорошо сделано". Я взял этот пример из Tutorials Point: http://www.tutorialspoint.com/cplusplus/cpp_switch_statement.htm