Лучше сказать x == Foo:: A || x == Foo:: B || x == Foo:: C ||...?
Скажем, у меня есть куча хорошо известных значений, вроде этого (но const char *
- просто пример, это может быть более сложным):
const char *A = "A", *B = "B", *C = "C", *D = "D", *E = "E", *F = "F", *G = "G";
Теперь скажем, что я хочу вести себя определенным образом, если результат некоторого выражения находится в подмножестве таких:
if (some_complicated_expression_with_ugly_return_type == A ||
some_complicated_expression_with_ugly_return_type == C ||
some_complicated_expression_with_ugly_return_type == E ||
some_complicated_expression_with_ugly_return_type == G)
{
...
}
Я нахожу, что набираю такие вещи достаточно часто, чтобы мне хотелось бы сократить его.
Если языком был Python, я мог бы легко сказать:
if some_complicated_expression_with_ugly_return_type in [A, C, E, G]:
...
Есть ли известный, переносимый способ выразить это аналогично в С++ 03?
Обратите внимание, что тип возврата сам по себе является уродливым (почти таким же уродливым, как тип возврата лямбда-выражения), поэтому я, конечно, не хочу для сохранения его в локальной переменной.
Но тип возврата не должен совпадать с типом константы - например, если тип возврата был std::string
, он не был бы неявно конвертируется в const char *
, но operator ==
было бы идеально для сравнения.
До сих пор лучшим решением я должен был сказать что-то вроде:
const char *items[] = { A, C, E, G };
if (std::find(items, items + sizeof(items) / sizeof(*items),
some_complicated_expression_with_ugly_return_type)
!= items + sizeof(items) / sizeof(*items))
{
...
}
но это довольно чертовски уродливо. Есть ли лучший способ, который также работает для не-POD?
Ответы
Ответ 1
Вы могли бы определить ваше текущее лучшее решение в шаблоне:
template<class A, class B, size_t n>
inline bool is_in(const A &a, B (&bs)[n]) {
return std::find(bs, bs + n, a) != bs + n;
}
который вы можете использовать как
X items[] = { A, C, E, G };
if (is_in(some_complicated_expression_with_ugly_return_type, items))
…
Ответ 2
Если у вас есть С++ 11:
auto res = some_complicated_expression_with_ugly_return_type;
if (res == A
|| res == C
|| res == E
|| res == G) {
}
Если нет, вы все равно можете исключить объявление типа с помощью функции шаблона:
template <class T>
bool matches(T t) {
return t == A || t == C || t == E || t == G;
}
if (matches(some_complicated_expression_with_ugly_return_type)) {
}
Ответ 3
Вы можете использовать switch
:
switch (some_complicated_expression_with_ugly_return_type) {
case A: case C: case E: case G:
// do something
default:
// no-op
}
Это работает только с целыми и перечисляемыми типами. Заметьте.
Для более сложных типов вы можете использовать С++ 11 auto
, или для С++ 03, boost BOOST_AUTO
:
auto tmp = some_complicated_expression_with_ugly_return_type;
// or
BOOST_AUTO(tmp, some_complicated_expression_with_ugly_return_type);
if (tmp == A || tmp == C || tmp == E || tmp == G) {
// ...
}
Ответ 4
(Edit: Оказывается, мой оригинальный трюк с фиктивным типом не работал, меня ввели в заблуждение случайным случаем в моих тестах. Попробуем еще раз...)
С помощью нескольких вспомогательных шаблонов вы можете написать общее решение для такого рода ситуаций:
template <typename T1> class Matcher {
public:
explicit Matcher(T1 t1): val(t1), flag(false) {}
template <typename T2> Matcher& operator()(T2 t2)
{ flag |= val == t2; return *this; }
operator bool() const { return flag; }
private:
T1 val;
bool flag;
};
template <typename T1> Matcher<T1> match(T1 t1) { return Matcher<T1>(t1); }
// example...
string s = whatever;
if (match(s)("foo")("bar")("zap")) { do_something(); }
Вы можете сопоставить столько аргументов, сколько хотите.
Ответ 5
Выражения типа
if (some_complicated_expression_with_ugly_return_type == A ||
some_complicated_expression_with_ugly_return_type == C ||
some_complicated_expression_with_ugly_return_type == E ||
some_complicated_expression_with_ugly_return_type == G)
{
...
}
довольно распространены в коде (ну, в любом случае, предварительно вычисленное выражение). Я думаю, что лучшее, что вы можете сделать для удобства чтения, - это предварительное вычисление выражения и сохранить его как.
ugly_return_type x = some_complicated_expression_with_ugly_return_type;
if (x == A ||
x == C ||
x == E ||
x == G)
{
...
}
Для этого типа синтаксиса используются разработчики. Это значительно облегчает понимание, когда кто-то читает ваш код
Он также выражает то, что вы хотите отлично. Там причина такого синтаксиса настолько широко используется в существующем коде, потому что другие альтернативы хуже для читаемости.
Конечно, вы можете обернуть условие в функцию, но только если оно многократно используется и логически имеет смысл (помимо точки IMO).
Ответ 6
Это можно сделать с помощью вариационных функций в С++ 03 следующим образом:
template <typename T>
bool MatchesOne( T _lhs, int _count, ...)
{
va_list vl;
va_start(vl,_count);
for (int i=0;i<_count;i++)
{
int rhs=va_arg(vl,int);
cout << "rhs = " << rhs << endl;
if (_lhs == rhs) return true;
}
va_end(vl);
return false;
}
int main(){
float ff = 3.0;
if (MatchesOne(ff, 5, 1, 2, 4, 5, 3))
{
cout << "Matches" << endl;
}
return 0;
}
Если вы знаете, что типы всех выражений будут иметь тот же тип, что и _lhs, вы можете изменить int rhs=va_arg(vl,int);
на T rhs=va_arg(vl,T);
Вы также можете сделать это элегантно, используя вариативные шаблоны в С++ 11:
template<typename T, typename T2>
bool one_equal(const T & _v1, const T2 & _v2)
{
return _v1 == _v2;
}
template<typename T, typename T2, typename... Args>
bool one_equal(const T & _v1, const T2 & _v2, Args... args)
{
return _v1 == _v2 || one_equal(_v1, args...);
}
...
if (one_equal(some_complicated_expression, v1, v2, v3, v4))
{
}
Хорошо, последнее решение хак-иша. Он работает, но делает реализатора этой функции много повторяющейся работы.
template <typename T1, typename T2>
bool match_one(T1 _v1, T2 _v2)
{
return _v1 == _v2;
}
template <typename T1, typename T2, typename T3>
bool match_one(T1 _v1, T2 _v2, T3 _v3)
{
return _v1 == _v3 || match_one(_v1, _v2);
}
template <typename T1, typename T2, typename T3, typename T4>
bool match_one(T1 _v1, T2 _v2, T3 _v3, T4 _v4)
{
return _v1 == _v4 || match_one(_v1, _v2, _v3);
}
template <typename T1, typename T2, typename T3, typename T4, typename T5>
bool match_one(T1 _v1, T2 _v2, T3 _v3, T4 _v4, T5 _v5)
{
return _v1 == _v5 || match_one(_v1, _v2, _v3, _v4);
}
Ответ 7
Если не переключиться, может быть, что-то вроде этого, я не использовал его, но может быть черновик чего-то работающего?
template <class ReturnType>
bool average(ReturnType expression, int count, ...)
{
va_list ap;
va_start(ap, count); //Requires the last fixed parameter (to get the address)
for(int j=0; j<count; j++)
if(expression==va_arg(ap, ReturnType))
return true;
return false
va_end(ap);
}
Ответ 8
С++ 11:
template<typename T1, typename T2>
bool equalsOneOf (T1&& value, T2&& candidate) {
return std::forward<T1>(value) == std::forward<T2>(candidate);
}
template<typename T1, typename T2, typename ...T>
bool equalsOneOf (T1&& value, T2&& firstCandidate, T&&...otherCandidates) {
return (std::forward<T1>(value) == std::forward<T2>(firstCandidate))
|| equalsOneOf (std::forward<T1> (value), std::forward<T>(otherCandidates)...);
}
if (equalsOneOf (complexExpression, A, D, E)) { ... }
С++ 03:
template<typename T, typename C>
bool equalsOneOf (const T& value, const C& c) { return value == c; }
template<typename T, typename C1, typename C2>
bool equalsOneOf (const T& value, const C1& c1, const C2& c2) {
return (value == c2) || equalsOneOf (value, c1);
}
template<typename T, typename C1, typename C2, typename C3>
bool equalsOneOf (const T& value, const C1& c1, const C2& c2, const C3& c3) {
return (value == c3) || equalsOneOf (value, c1, c2);
}
template<typename T, typename C1, typename C2, typename C3, typename C4>
bool equalsOneOf (const T& value, const C1& c1, const C2& c2, const C3& c3, const C4& c4) {
return (value == c4) || equalsOneOf (value, c1, c2, c3);
}
template<typename T, typename C1, typename C2, typename C3, typename C4, typename C5>
bool equalsOneOf (const T& value, const C1& c1, const C2& c2, const C3& c3, const C4& c4, const C5& c5) {
return (value == c5) || equalsOneOf (value, c1, c2, c3, c4);
}
// and so on, as many as you need