LINUX.ORG.RU

В с++ как красиво проверить значение на вхождение в список инициализации?

 


0

3

Как можно наиболее красиво проверить значение на вхождение в список указанный как список инициализации?

Что бы удобно было выполнять подобные или другие условные конструкции?

void my_func(uint a, MyEnum b) {
   assert( a belong {0,1,2,3} && b belong {MyEnum::def1,MyEnum::def2} );
}

==========================================================

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

template <typename T, typename... Args>
bool belong(const T &t, const Args &... args) {
    return ((t == args) || ...);
}

...
if (belong(a, MyEnum::v1, MyEnum::v2, MyEnum::v3))
   do_something();

Возможен вариант без дополнительных функций:

if (std::set({MyEnum::v1,MyEnum::v2}).count(b))
   do_something();

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

template <class T>
bool belong(const T &v, const std::initializer_list<T> &list) {
    auto itr = std::find(list.begin(), list.end(), v);
    return itr != list.end();
}

...
if (belong(a, {MyEnum::v1,MyEnum::v2}))
   do_something();



Последнее исправление: victor79 (всего исправлений: 3)

(a == 0 || a == 1 || a == 2 || a == 3 ) && (b == def1 || b == def2)

В c++ нужна не красота, а скорость. Варианты с созданием промежуточной структуры данных и поиском в ней будут медленнее на небольшом числе элементов. Как минимум вариант выше ленивый

Crocodoom ★★★★★
()
Ответ на: комментарий от Crocodoom

Если значений чуть побольше, то можно построить промежуточную структуру данных во время компиляции или первого вызова функции. Но где грань между «небольшим» и «побольше», надо профилировать.

filosofia
()
Ответ на: комментарий от Crocodoom

Это спорный вопрос. Я давно уже забил оптимизировать g++ по мелочам. Потому что много раз проверял, сначала изголяешься, делаешь наиболее оптимизированные выражения, а после хотя бы временно для проверки заменяешь на компактное, а оно работает с такой же скоростью... Главное лишних циклов не делать, и циклов в циклах.

Подумав минутку на ум пришла такая функция (пока вроде компилится):

template <class T>
bool belong(const T &v, const std::initializer_list<T> &list) {
    auto itr = std::find(list.begin(), list.end(), v);
    return itr != list.end();
}

...

assert( belong(a, {1,2,3}) );

Есть другие варианты?

victor79
() автор топика
Ответ на: комментарий от victor79

Вместо списка инициализации можешь взять set, поиск будет логарифмический. Но на пяти элементах это все фигня, ты больше потратишь на переходы по указателям.

filosofia
()
Ответ на: комментарий от filosofia

И на построение set.

Тут больше вопрос в конструкции, а не в скорости. Может можно это сделать через оператор, а не через функцию?

victor79
() автор топика
Ответ на: комментарий от anonymous

Жду когда надо будет подтягивать фреймворк или либу для этих целей, а пока не мало смузи.

anonymous
()
Ответ на: комментарий от victor79

Может можно это сделать через оператор, а не через функцию?

Что мешает написать оператор, раз получилось написать функцию?

anonymous
()
Ответ на: комментарий от filosofia

По-хорошему тут нужны нормальные макросы, чтобы красивый вызов {1,2,3,4}.search(4) разворачивался под капотом в лапшу из (||) или в логарифмический поиск — в зависимости от числа элементов (и настроек компиляции)

Crocodoom ★★★★★
()
Последнее исправление: Crocodoom (всего исправлений: 1)

Тут кто-то постил на вариадик темплейт, но удалил. Вот проверенный вариант:


template <typename T1, typename T2>
bool belong(const T1 &v1, const T2 &v2) {
    return v1 == v2;
}

template <typename T1, typename T2, typename ...Tail>
bool belong(const T1 &v1, const T2 &v2, const Tail &...tail) {
    return belong(v1, v2) || belong(v1, tail...);
}

...
assert( belong(a, 1, 2, 3) );
И оптимизировать ничего не нужно...

victor79
() автор топика
#include <iostream>

template <typename T, typename... Args>
bool belong(T t, Args... args) { return ((t == args) || ...); }

enum class Foo { one, two, three, four };

int main()
{
    auto x = Foo::one;
    std::cout << belong(x, Foo::one, Foo::two) << "\n";
    std::cout << belong(x, Foo::two, Foo::three, Foo::four) << "\n";
    return 0;
}
AlexVR ★★★★★
()
Ответ на: комментарий от anonymous

если с++20, то можно std::set{MyEnum::def1,MyEnum::dev2}.contains(b)

без с++20: set{MyEnum::def1,MyEnum::dev2}.count(b) То же неплохо, но уже не так красиво.

Что мешает написать оператор, раз получилось написать функцию?

Попробовал, описать оператор получилось, а вот в применении компилятор выдает ошибку непонятную:

template <class T1, class T2>
bool operator == (const T1 &v, const std::initializer_list<T2> &list) {
    auto itr = std::find(list.begin(), list.end(), v);
    return itr != list.end();
}

assert( a == {1,2,3} );

error: macro "assert" passed 3 arguments, but takes just 1
  103 |         assert( x == {1,2,3} );
victor79
() автор топика
Ответ на: комментарий от anonymous

Ну хорошо, если так. Но вопрос о переходе к логарифмическому поиску на «большом» числе элементов остается

Crocodoom ★★★★★
()
Ответ на: комментарий от Crocodoom

Ну хорошо, если так. Но вопрос о переходе к логарифмическому поиску на «большом» числе элементов остается

Представляю такой исходник с++, на несколько килобайт перечисление множества на проверку...

victor79
() автор топика
Ответ на: комментарий от AlexVR

Только лучше универсальные ссылки использовать, а то что-то тяжелое будет копипаститься

bool belong(T&& t, Args&&... args)
PRN
()
Ответ на: комментарий от victor79

error: macro «assert» passed 3 arguments, but takes just 1

Тебе по слогам прочитать описание ошибки. Подсказка: скобки.

anonymous
()
Ответ на: комментарий от AlexVR

Тогда уж вот так:

template <typename T, typename... Args>
bool belong(const T &t, const Args &... args) { return ((t == args) || ...); }

Пока самый лучший вариант.

victor79
() автор топика
Ответ на: комментарий от anonymous

Тебе по слогам прочитать описание ошибки. Подсказка: скобки.

Все равно не понял. Вы бы продемонстрировали рабочий вариант...

victor79
() автор топика
Ответ на: комментарий от victor79

Все равно не понял.

Показываю, как видит компилятор

assert( a == {1 , 2 , 3} ); // в макрос передается 3 аргумента "a == {1",   "2",    "3}"

Ты используешь assert, не понимая что это такое и как работает. Подсказка: скобки.

anonymous
()
Ответ на: комментарий от victor79

error: expected primary-expression before ‘{’ token

Хотя, да, initializer_list не получится использовать с операторами без явного указания

anonymous
()
Ответ на: комментарий от victor79

Есть другие варианты?

std::initializer_list нужно передавать по значению, а не по ссылке.

Так же как и T, если T простой тип…

fsb4000 ★★★★★
()
Ответ на: комментарий от fsb4000

нужно передавать по значению, а не по ссылке.

Так же как и T, если T простой тип…

Не завидую тебе: это сколько шаблонов с шаблонной магией тебе придется писать ради этого?

anonymous
()
Ответ на: комментарий от anonymous

с макросами там на самом деле мало писать. Но это только для T.

initializer_list всегда нужно передавать по значению вместо const&, так как это просто два указателя, вне зависимости от внутреннего типа в initializer_list<T>.

initializer_list не отличается от других view типов, типа string_view..

И во всех примерах на cppreference и в стандарте делают именно так:

https://en.cppreference.com/w/cpp/utility/initializer_list

https://gcc.godbolt.org/z/5G3WzP

fsb4000 ★★★★★
()
Последнее исправление: fsb4000 (всего исправлений: 1)
Ответ на: комментарий от fsb4000

нужно передавать по значению вместо const&

Что будет, если передать const&?

initializer_list … это просто два указателя

Ссылка на стандарт будет?

Сколько реализаций надо копипастить и поддерживать эти копипасты, если нужна шаблонная функция для любых «контейнеров», к которым применим std::begin std::end?

anonymous
()
Ответ на: комментарий от anonymous

Что будет, если передать const&?

Если не будет инлайнинга, то будет чуть медленнее, из-за лишней косвенности. Хотя я написал это даже не столько из-за производительности, сколько это неправильно стилистически. Значит человек не понимает с чем работает. Нужно различать легкие proxy view типы и владеющие ресурсом типы.

Ссылка на стандарт будет?

держи, можешь читать весь https://isocpp.org/files/papers/N4860.pdf

Если не поймёшь, то запишись на курсы, там тебя будут учить С++.

fsb4000 ★★★★★
()
Ответ на: комментарий от fsb4000

Если не будет инлайнинга, то будет чуть медленнее

Запихивание структуры вместо одного значения - это быстрее или медленнее?

Правильнее так: вызов конструктора копий - это быстрее или медленнее? Упустим исключения при вызове конструктора, так как повезло, что конструктор init-list’а не кидает исключения.

это неправильно стилистически

Стиляга, это все претензии?

Но да, я соглашусь с отсутствием стиля. Но претензии тут к стилю языка, который засталяет писать const&, && и другие закорючки. А не к стилю бедняги-программиста.

держи

И где там сказано, что - это всего лишь два указателя?

anonymous
()
Ответ на: комментарий от anonymous

И где там сказано, что - это всего лишь два указателя?

https://imgur.com/a/4lV652m

  • все примеры в стандарте передают initializer_list по значению

  • во всех трёх библиотеках (MSVC, stdlibc++, libc++) он реализован как 2 указателя.

fsb4000 ★★★★★
()
Ответ на: комментарий от fsb4000

https://imgur.com/a/4lV652m

Пример из стандарта - веский агрумент.

все примеры в стандарте передают initializer_list по значению

Пример из стандарта - веский агрумент.

во всех трёх библиотеках (MSVC, stdlibc++, libc++) он реализован как 2 указателя.

Покажи 2 указателя.

gcc-10

  template<class _E>
    class initializer_list
    {
...
    private:
      iterator                  _M_array;
      size_type                 _M_len;  

Что там понапихано в дебажных версиях, я не смотел.

anonymous
()
Ответ на: комментарий от fsb4000

указатель и длина тоже самое что и два указателя.

Вопросов к «знатоку стандарта и его реализаций» больше не имею.

anonymous
()
Ответ на: комментарий от anonymous

По памяти это +/- тоже самое (но как правило тоже самое)

PRN
()

Сильно просто. Это-же не наш метод! Вот здесь объяснено почему и дано по-настоящему профессиональное решение: https://youtu.be/Zqpr6aOAmlI?t=267

Можешь сразу перематывать на 45-ю минуту.

rupert ★★★★★
()
Последнее исправление: rupert (всего исправлений: 1)
Ответ на: комментарий от victor79

Нет. && – универсальная ссылка, и будет принимать как мутабельные, так и const аргументы, сохраняя их свойства.

Siborgium ★★★★★
()
Ответ на: комментарий от Siborgium

Но мутабельные то здесь совсем не нужны. Так же как и конструкторы копирования, если совсем без ссылки. Будет ли более правильным const&&, чем const& ? Я так понимаю, что компилятор все равно скомпилит это в одинаковый бинарный код.

victor79
() автор топика
Последнее исправление: victor79 (всего исправлений: 1)
Ответ на: комментарий от victor79

Так же как и конструкторы копирования, если совсем без ссылки.

Там не будет копирования.

Будет ли более правильным const&&, чем const&

Нет. auto&& – т.н. универсальная ссылка (более правильная, но менее популярная версия – forwarding reference), сохраняющая свойства аргумента. const auto&& таковой не является и в целом кроме как для перегрузки особого смысла не имеет. Итого, здесь либо принимать по const auto&, либо по auto&&.

Siborgium ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.