Как записать такое в современном C++?
От: Alekzander  
Дата: 17.06.23 03:27
Оценка:
Тут недавно была статья от создателей PVS, как надо и не надо писать код. В ней автор приводил следующий пример с ошибкой:

void adns__querysend_tcp(adns_query qu, struct timeval now) {
  ...
  if (!(errno == EAGAIN || EWOULDBLOCK || 
        errno == EINTR || errno == ENOSPC ||
        errno == ENOBUFS || errno == ENOMEM)) {
  ...
}


А чтобы, значит, таких ошибок не допускать, он предлагал форматирование.

if (!(   errno == EAGAIN
      || EWOULDBLOCK
      || errno == EINTR
      || errno == ENOSPC
      || errno == ENOBUFS
      || errno == ENOMEM)) {


С моей точки зрения это глупость. Код надо писать так, чтобы его нельзя было записать неправильно хоть в строчку, хоть столбиком, табличкой, крестиком, козликом и т.д. Соответственно, единственный правильный вариант это вот такой вот псевдокод:

if (!(errno is in (
                       EAGAIN,
                       EWOULDBLOCK,
                       EINTR,
                       ENOSPC,
                       ENOBUFS,
                       ENOMEM
                   ))
{
...
}


Как это записать в современном C++, чтобы не было performance penalty? Без конструирования контейнера и т.п. Нормальных макросов же (как в Немерле), насколько я понимаю, не завезли?
Re: Как записать такое в современном C++?
От: vopl Россия  
Дата: 17.06.23 05:31
Оценка: 124 (7) +5
A>А чтобы, значит, таких ошибок не допускать, он предлагал форматирование.

A>
A>if (!(   errno == EAGAIN
A>      || EWOULDBLOCK
A>      || errno == EINTR
A>      || errno == ENOSPC
A>      || errno == ENOBUFS
A>      || errno == ENOMEM)) {
A>


A>С моей точки зрения это глупость.


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

A>Код надо писать так, чтобы его нельзя было записать неправильно хоть в строчку, хоть столбиком, табличкой, крестиком, козликом и т.д. Соответственно, единственный правильный вариант это вот такой вот псевдокод:


A>
A>if (!(errno is in (
A>                       EAGAIN,
A>                       EWOULDBLOCK,
A>                       EINTR,
A>                       ENOSPC,
A>                       ENOBUFS,
A>                       ENOMEM
A>                   ))
A>{
A>...
A>}
A>


A>Как это записать в современном C++, чтобы не было performance penalty? Без конструирования контейнера и т.п. Нормальных макросов же (как в Немерле), насколько я понимаю, не завезли?


например

#include <errno.h>

template <auto... set>
bool isin(auto val)
{
    return (false || ... || (set == val));
}

int main() 
{
    // 
    if(isin<EAGAIN,
            EWOULDBLOCK,
            EINTR,
            ENOSPC,
            ENOBUFS,
            ENOMEM>(errno))
    {
        return 0;
    }

    return 1;
}
Re: Как записать такое в современном C++?
От: fk0 Россия https://fk0.name
Дата: 17.06.23 07:24
Оценка:
Здравствуйте, Alekzander, Вы писали:

A>Тут недавно была статья от создателей PVS, как надо и не надо писать код. В ней автор приводил следующий пример с ошибкой:


A>
A>void adns__querysend_tcp(adns_query qu, struct timeval now) {
A>  ...
A>  if (!(errno == EAGAIN || EWOULDBLOCK || 
A>        errno == EINTR || errno == ENOSPC ||
A>        errno == ENOBUFS || errno == ENOMEM)) {
A>  ...
A>}
A>


switch-case. В голом C даже.
Re[2]: Как записать такое в современном C++?
От: Alekzander  
Дата: 17.06.23 07:58
Оценка:
Здравствуйте, vopl, Вы писали:

V>например


V>
V>#include <errno.h>

V>template <auto... set>
V>bool isin(auto val)
V>{
V>    return (false || ... || (set == val));
V>}

V>int main() 
V>{
V>    // 
V>    if(isin<EAGAIN,
V>            EWOULDBLOCK,
V>            EINTR,
V>            ENOSPC,
V>            ENOBUFS,
V>            ENOMEM>(errno))
V>    {
V>        return 0;
V>    }

V>    return 1;
V>}
V>


Спасибо, сразу вопросы. Лишний call выкинут все компиляторы? Есть стандартная реализация? Доколе шаблоны вместо макросов?
Re[2]: Как записать такое в современном C++?
От: Alekzander  
Дата: 17.06.23 08:01
Оценка:
Здравствуйте, fk0, Вы писали:

A>>Тут недавно была статья от создателей PVS, как надо и не надо писать код. В ней автор приводил следующий пример с ошибкой:


A>>
A>>void adns__querysend_tcp(adns_query qu, struct timeval now) {
A>>  ...
A>>  if (!(errno == EAGAIN || EWOULDBLOCK || 
A>>        errno == EINTR || errno == ENOSPC ||
A>>        errno == ENOBUFS || errno == ENOMEM)) {
A>>  ...
A>>}
A>>


fk0> switch-case. В голом C даже.


Предикат через switch-case... такое. Тем более, в данном случае по невнимательности ошибок ещё больше настряпаешь, чем с простым if'ом.
Re[2]: Как записать такое в современном C++?
От: serg_joker Украина  
Дата: 17.06.23 08:21
Оценка: +3
Здравствуйте, vopl, Вы писали:

Унарной свёртки достаточно:
return ( ... || (set == val));
Re[3]: Как записать такое в современном C++?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 17.06.23 08:36
Оценка:
Здравствуйте, Alekzander, Вы писали:

fk0>> switch-case. В голом C даже.


A>Предикат через switch-case... такое. Тем более, в данном случае по невнимательности ошибок ещё больше настряпаешь, чем с простым if'ом.


inline-функция или лямбда (без захвата контекста), соптимизируется с ходу.
The God is real, unless declared integer.
Re[2]: Как записать такое в современном C++?
От: so5team https://stiffstream.com
Дата: 17.06.23 09:21
Оценка: 3 (1) +1
Здравствуйте, fk0, Вы писали:

A>>
A>>void adns__querysend_tcp(adns_query qu, struct timeval now) {
A>>  ...
A>>  if (!(errno == EAGAIN || EWOULDBLOCK || 
A>>        errno == EINTR || errno == ENOSPC ||
A>>        errno == ENOBUFS || errno == ENOMEM)) {
A>>  ...
A>>}
A>>


fk0> switch-case. В голом C даже.


Если написать в лоб:
void adns__querysend_tcp(adns_query qu, struct timeval now) {
  auto is_err = false;
  switch(errno) {
    case EAGAIN:
    case EWOULDBLOCK:
    case EINTR:
    case ENOSPC:
    case ENOBUFS:
    case ENOMEM:
      is_err =  true;
  }
  if (!is_err) {

то возникнет ошибка компиляции на платформах, где EAGAIN == EWOULDBLOCK. Тогда как на платформах, где это разные значения, все будет нормально.

При этом тупое сравнение (errno == EAGAIN || errno == EWOULDBLOCK || ...) будет нормально компилироваться всегда.
Re[3]: Как записать такое в современном C++?
От: so5team https://stiffstream.com
Дата: 17.06.23 09:29
Оценка:
Здравствуйте, serg_joker, Вы писали:

_>Здравствуйте, vopl, Вы писали:


_>Унарной свёртки достаточно:

_>
_>return ( ... || (set == val));
_>


Интересно, а какое тогда значение будет возвращено при вот таком вызове: isin<>(errno) и почему?
Re[4]: Как записать такое в современном C++?
От: vopl Россия  
Дата: 17.06.23 09:36
Оценка: 10 (1) +2
Здравствуйте, so5team, Вы писали:

S>Здравствуйте, serg_joker, Вы писали:


_>>Здравствуйте, vopl, Вы писали:


_>>Унарной свёртки достаточно:

_>>
_>>return ( ... || (set == val));
_>>


S>Интересно, а какое тогда значение будет возвращено при вот таком вызове: isin<>(errno) и почему?


https://timsong-cpp.github.io/cppwp/temp.variadic#tab:temp.fold.empty
Re[5]: Как записать такое в современном C++?
От: so5team https://stiffstream.com
Дата: 17.06.23 09:39
Оценка: +1
Здравствуйте, vopl, Вы писали:

S>>Интересно, а какое тогда значение будет возвращено при вот таком вызове: isin<>(errno) и почему?


V>https://timsong-cpp.github.io/cppwp/temp.variadic#tab:temp.fold.empty


Большое спасибо!

Для себя сделаю вывод, что если стандарт не вызубрен наизусть, то безопаснее все-таки написать (false || ... || (set == val))
Re[3]: Как записать такое в современном C++?
От: serg_joker Украина  
Дата: 17.06.23 10:09
Оценка: 6 (2)
Здравствуйте, Alekzander, Вы писали:


A>Спасибо, сразу вопросы. Лишний call выкинут все компиляторы?

Я бы посмотрел на те компиляторы, которые интересны. Крайне рекомендую Compiler explorer для подобных исследований.
Например, по ссылке выше можно увидеть, что gcc/clang делают встроенный код, идентичный "ручному" сравнению для подходов через variadic non-type temlate args и через initializer_list.
При этом, для подхода через массив значений (он может быть удобен для случаев, когда нужно дать имя списку значений для переиспользования, и когда не хочется городить именнованные компайл-тайм списки) gcc даёт такой же код, а clang — таки делает массив.
А мсвц делает код "идентичный натуральному" только при подходе через variadic non-type temlate args, а для initializer_list и массива вставляет поиск (`call __std_find_trivial_4`)

A>Есть стандартная реализация?

Я такой не знаю. Для массива и initializer_list (см. примеры по ссылке выше) можно использовасть std::ranges::contains, но код получается хуже, чем через std::find. Впрочем, для msvc он один фиг неоптимальный.
Re[6]: Как записать такое в современном C++?
От: serg_joker Украина  
Дата: 17.06.23 10:54
Оценка: +1
Здравствуйте, so5team, Вы писали:

S>Для себя сделаю вывод, что если стандарт не вызубрен наизусть, то безопаснее все-таки написать (false || ... || (set == val))


Ну тут же логично всё, на мой взгляд, без зазубривания.

`||...` читай как "среди перечисленного существует такое, что...". Если список "перечисленного" пуст, то и не "существует такое что".
`&&...` читай как "для каждого перечисленного верно...". Если список "перечисленного" пуст, то для всех перечисленных условие выполняется (не существует такого в перечисленном, для которого не выполняется).
Отредактировано 17.06.2023 10:57 serg_joker . Предыдущая версия .
Re[4]: Как записать такое в современном C++?
От: Alekzander  
Дата: 17.06.23 11:31
Оценка:
Здравствуйте, serg_joker, Вы писали:

A>>Спасибо, сразу вопросы. Лишний call выкинут все компиляторы?

_>Я бы посмотрел на те компиляторы, которые интересны. Крайне рекомендую Compiler explorer для подобных исследований.
_>Например, по ссылке выше можно увидеть, что gcc/clang делают встроенный код, идентичный "ручному" сравнению для подходов через variadic non-type temlate args и через initializer_list.
_>При этом, для подхода через массив значений (он может быть удобен для случаев, когда нужно дать имя списку значений для переиспользования, и когда не хочется городить именнованные компайл-тайм списки) gcc даёт такой же код, а clang — таки делает массив.
_>А мсвц делает код "идентичный натуральному" только при подходе через variadic non-type temlate args, а для initializer_list и массива вставляет поиск (`call __std_find_trivial_4`)

Честно сказать, я ждал ответа, что даже не сомневайся, все делают! А если надо посмотреть, всё плохо Но всё равно спасибо за ссылку!

A>>Есть стандартная реализация?

_>Я такой не знаю. Для массива и initializer_list (см. примеры по ссылке выше) можно использовасть std::ranges::contains, но код получается хуже, чем через std::find. Впрочем, для msvc он один фиг неоптимальный.

Чего только нет в std... Ничего нет!
Re[5]: Как записать такое в современном C++?
От: serg_joker Украина  
Дата: 17.06.23 11:54
Оценка:
Здравствуйте, Alekzander, Вы писали:

A>Честно сказать, я ждал ответа, что даже не сомневайся, все делают! А если надо посмотреть, всё плохо Но всё равно спасибо за ссылку!

Сомневаться и проверять вообще полезно, не только в программировании.

A>Чего только нет в std... Ничего нет!

В std Много чего нет, но много чего и есть. И список этого "чего есть" растёт, хоть и не с такой скоростью (или не с тем качеством), как этого кому-нибудь может хотеться.
А есть языки, в которых есть всё, чего хочется и в самом языке, и в стандартной библиотеке? Если да, то будет разумным переходить на них.
Если нет, то стоит принять неидеальность рабочего инструмента и развивать свои навыки его использования.
Re[5]: Как записать такое в современном C++?
От: flаt  
Дата: 18.06.23 09:33
Оценка:
Здравствуйте, vopl, Вы писали:


V>https://timsong-cpp.github.io/cppwp/temp.variadic#tab:temp.fold.empty


false для пустого || логичен, а вот true для пустого && весьма спорно, однако.
Re: Как записать такое в современном C++?
От: student__  
Дата: 19.06.23 08:55
Оценка: +2
Здравствуйте, Alekzander, Вы писали:

A>С моей точки зрения это глупость. Код надо писать так, чтобы его нельзя было записать неправильно хоть в строчку, хоть столбиком, табличкой, крестиком, козликом и т.д.


Юношеский максимализм.
Re[6]: Как записать такое в современном C++?
От: Кодт Россия  
Дата: 19.06.23 09:28
Оценка: +3
Здравствуйте, flаt, Вы писали:

V>>https://timsong-cpp.github.io/cppwp/temp.variadic#tab:temp.fold.empty


F>false для пустого || логичен, а вот true для пустого && весьма спорно, однако.


Не спорно.

1) Законы Де Моргана.

OR(p1,...,pn) = not AND(not p1, ..., not pn)

2) Алгебра.

OP(p1,...,pn) = e op OP(p1,...,pn) = OP(e,p1,...,pn) = e op p1 op ... op pn

Для or нейтралью является false, для and — true.
(А также — для сложения 0, для умножения 1).
Перекуём баги на фичи!
Re: Как записать такое в современном C++?
От: BSOD  
Дата: 19.06.23 09:34
Оценка: :)
Здравствуйте, Alekzander, Вы писали:

A>Как это записать в современном C++, чтобы не было performance penalty? Без конструирования контейнера и т.п. Нормальных макросов же (как в Немерле), насколько я понимаю, не завезли?


switch(errno)
    case EAGAIN:
    case EWOULDBLOCK:
    case EINTR:
    case ENOSPC:
    case ENOBUFS:
    case ENOMEM:
    {...}
Sine vilitate, sine malitiosa mente
Re[2]: Как записать такое в современном C++?
От: Alekzander  
Дата: 20.06.23 06:21
Оценка:
Здравствуйте, student__, Вы писали:

A>>С моей точки зрения это глупость. Код надо писать так, чтобы его нельзя было записать неправильно хоть в строчку, хоть столбиком, табличкой, крестиком, козликом и т.д.


__>Юношеский максимализм.


Сочувствую.
Re[2]: Как записать такое в современном C++?
От: Alekzander  
Дата: 20.06.23 06:23
Оценка:
Здравствуйте, BSOD, Вы писали:

A>>Как это записать в современном C++, чтобы не было performance penalty? Без конструирования контейнера и т.п. Нормальных макросов же (как в Немерле), насколько я понимаю, не завезли?


BSO>
BSO>switch(errno)
BSO>    case EAGAIN:
BSO>    case EWOULDBLOCK:
BSO>    case EINTR:
BSO>    case ENOSPC:
BSO>    case ENOBUFS:
BSO>    case ENOMEM:
BSO>    {...}
BSO>


Выше написали, что при дублировании будет ошибка компиляции. А от себя добавлю, что ! выражать через default это верный способ сделать больше ошибок, а не меньше.
Re[5]: Как записать такое в современном C++?
От: Maniacal Россия  
Дата: 20.06.23 08:05
Оценка: +1
Здравствуйте, Alekzander, Вы писали:

A>Чего только нет в std... Ничего нет!


Похоже на отсылку к Жванецкому

У нас чего только может не быть. У нас всего может не быть. У нас чего только не захочешь, того может и не быть.

— Михаил Жванецкий
Re: Как записать такое в современном C++?
От: Dair Россия https://dair.spb.ru
Дата: 20.06.23 08:39
Оценка:
Здравствуйте, Alekzander, Вы писали:

A> if (!(errno == EAGAIN || EWOULDBLOCK ||

A> errno == EINTR || errno == ENOSPC ||
A> errno == ENOBUFS || errno == ENOMEM)) {

Коллеги выше красиво написали, но почему-то упустили тот факт, что EWOULDBLOCK не находится в списке значений, которые не должен принимать errno.
Re[2]: Как записать такое в современном C++?
От: Sm0ke Россия ksi
Дата: 30.06.23 19:13
Оценка: 2 (1)
Здравствуйте, Dair, Вы писали:

D>Здравствуйте, Alekzander, Вы писали:


A>> if (!(errno == EAGAIN || EWOULDBLOCK ||

A>> errno == EINTR || errno == ENOSPC ||
A>> errno == ENOBUFS || errno == ENOMEM)) {

D>Коллеги выше красиво написали, но почему-то упустили тот факт, что EWOULDBLOCK не находится в списке значений, которые не должен принимать errno.


Этот фрагмент, как упоминается в исходной статье от студии PVS, содержит ошибку.

В исправленной версии там именно сравнение с errno.
Re[2]: Как записать такое в современном C++?
От: Sm0ke Россия ksi
Дата: 30.06.23 19:18
Оценка:
Здравствуйте, vopl, Вы писали:

V>например


V>
V>#include <errno.h>

V>template <auto... set>
V>bool isin(auto val)
V>{
V>    return (false || ... || (set == val));
V>}

V>int main() 
V>{
V>    // 
V>    if(isin<EAGAIN,
V>            EWOULDBLOCK,
V>            EINTR,
V>            ENOSPC,
V>            ENOBUFS,
V>            ENOMEM>(errno))
V>    {
V>        return 0;
V>    }

V>    return 1;
V>}
V>


Вот бы уменьшить число сравнений с помощью бинарного поиска в упорядоченном ряде значений.
Взять какой-нибудь constexpr set и вызвать метод contains().

Есть например https://github.com/serge-sans-paille/frozen , но я его не пробовал.
Отредактировано 30.06.2023 19:19 Sm0ke . Предыдущая версия .
Re: Как записать такое в современном C++?
От: B0FEE664  
Дата: 03.07.23 13:30
Оценка: 3 (1)
Здравствуйте, Alekzander, Вы писали:

A>Как это записать в современном C++, чтобы не было performance penalty?


Для такого случая у меня есть специальный класс:
template<class T>
class The
{
    public:
        constexpr explicit The(const T& x) noexcept
          : _x(x)
        {
        }

        template <class... TArgs>
        bool one_of(TArgs&&... args) const
        {
            return ((_x == args) || ...);
        }

        template <auto... VArgs>
        bool one_of() const
        {
            return ((_x == VArgs) || ...);
        }

    public:
        const T& _x;
};


На мой взгляд такой код легче читать, чем вариант с функцией
Автор: vopl
Дата: 17.06.23
:
int main() 
{
    // 
    if ( The(errno).one_of<
                           EAGAIN,
                           EWOULDBLOCK,
                           EINTR,
                           ENOSPC,
                           ENOBUFS,
                           ENOMEM
                         >()
       )
    {
        return 0;
    }

    return 1;
}
И каждый день — без права на ошибку...
Re[2]: Как записать такое в современном C++?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 03.07.23 14:22
Оценка:
Здравствуйте, BSOD, Вы писали:

BSO>switch(errno)

BSO> case EAGAIN:
BSO> case EWOULDBLOCK:
BSO> case EINTR:
BSO> case ENOSPC:
BSO> case ENOBUFS:
BSO> case ENOMEM:
BSO> {...}

Тут есть одна проблемка: на большинстве систем коды EAGAIN и EWOULDBLOCK сейчас совпадают (такое вот легаси), но есть специфические, где они различны. При совпадении switch выдаёт ошибку.
Можно проверять препроцессором, но это ещё больше левых слов.
А в варианте с == проблемы не возникает
The God is real, unless declared integer.
Re[3]: Как записать такое в современном C++?
От: kov_serg Россия  
Дата: 03.07.23 15:46
Оценка:
Здравствуйте, Sm0ke, Вы писали:

S>Вот бы уменьшить число сравнений с помощью бинарного поиска в упорядоченном ряде значений.

S>Взять какой-нибудь constexpr set и вызвать метод contains().
C++ и так это способен оптимизировать до битовых полей, что бы сравнивать группами.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.