c++ matches like rust
От: reversecode google
Дата: 13.11.23 13:34
Оценка: 4 (1)
перевожу на С++ код с раста
там есть прикольный макрос matches
накидал по быстрому более менее похожее
из TODO это добавить std::ignore для пропуска аргументов
может кто улучит или дополнит

еще по правильному бы сделать static_assert в matches до вызова _impl на размер массива и количество аргументов
я начал было делать и он работает
но для этого нужно было делать больше перегрузок для span и обычного массива
иначе размерность не достать
а делать на одном span, нельзя в чистую захватить обычный массив
вообщем оставил с обычной ссылкой, котрая матчит и то и другое
по мере разгребания конюшен раста, может что то усовершенствую
а расте он конечно более мощнее и навороченее, полностю врядли можно повторить на плюсах такое

https://godbolt.org/z/67r7dadja
#include <cstdlib>
#include <iostream>
#include <memory>
#include <cassert>
#include <array>
#include <span>

template<typename T>
constexpr auto in(T min, T max)
 { return [=](const auto& x){ return min<=x && x<=max; }; }

template<typename T, typename Arg>
bool cmp(const T & arr, int idx, Arg && val)
{
    if constexpr (!std::is_integral<Arg>::value)
            return val(arr[idx]);
    else    return arr[idx] == val;
}

template<typename T, typename... Args, std::size_t... Is>
bool matches_impl(const T & arr, std::index_sequence<Is...>, Args && ...values)
{
    return (cmp(arr,Is,std::forward<Args>(values)) && ...);
}

template<typename T,  typename... Args>
bool matches(const T & arr, Args && ...values)
{
    return matches_impl(arr,
                   std::index_sequence_for<Args...>{}, 
                   std::forward<Args>(values)...);
}

int main()
{
    int a[3]={1,2,3};
    std::array<int,4> aa{1,2,3,4};
    assert(matches(std::span(a), [](int v){return v==1;},2,in(1,5)) );
    assert(matches(std::span(a), 1,2,3) );

    assert(matches(std::span(aa), 1,2,3,[](auto v){return v>3;}) );
    assert(matches(aa, 1,2,3,[](auto v){return v>3;}) );

    assert(matches(std::span(a), 1,2,3,[](auto v){return v>3;}) );
}
Отредактировано 13.11.2023 15:56 reversecode . Предыдущая версия .
Re: c++ matches like rust
От: kov_serg Россия  
Дата: 13.11.23 14:19
Оценка:
Здравствуйте, reversecode, Вы писали:

R>перевожу на С++ код с раста

R>там есть прикольный макрос matches
R>накидал по быстрому более менее похожее
R>из TODO это добавить std::ignore для пропуска аргументов
R>может кто улучит или дополнит

"Program terminated with signal: SIGSEGV" не смущает?
Re[2]: c++ matches like rust
От: reversecode google
Дата: 13.11.23 14:48
Оценка:
нет
это последний ассерт, показать что оно работает
не.. можно конечно было сравнить с false
но так не интересно
Re: c++ matches like rust
От: sergii.p  
Дата: 13.11.23 15:56
Оценка:
Здравствуйте, reversecode, Вы писали:

R>может кто улучит или дополнит


не работает для enum class, enum и пр struct. Можно заменить на что-то такое

template<typename T, typename Arg>
bool cmp(const T & arr, int idx, Arg && val)
{
    if constexpr (std::is_invocable<Arg, decltype(arr[idx])>::value)
            return val(arr[idx]);
    else    return arr[idx] == val;
}
Re[2]: c++ matches like rust
От: reversecode google
Дата: 13.11.23 16:00
Оценка:
я сначала думал как лямбду поматчить
потом пробежался по треитах
и решил не ломать мозг и взять инверсию
инвокейбл был открыт третей закладой из всех треитов которые хотел попробовать
так что я до него не дошел что бы проверить

нужно весь переводимый с раста функционал завести было
Re[3]: c++ matches like rust
От: watchmaker  
Дата: 13.11.23 16:09
Оценка: +1
Здравствуйте, reversecode, Вы писали:

_>>"Program terminated with signal: SIGSEGV" не смущает?


R>нет ;)

R>это последний ассерт, показать что оно работает
R>не.. можно конечно было сравнить с false

При чём тут сравнение? У тебя происходит падение из-за проезда по памяти ещё до проверки результата assert'ом — https://godbolt.org/z/1rv7jKGcr

R> можно конечно было сравнить с false

R> но так не интересно

Если это тест, то пиши в проверяемом условии то, что должно выполняться — и другим было бы понятнее, и тебе бы писали больше по делу, а не про глупые ошибки или про стиль.


Иногда, конечно, в системах тестирования нужно уметь сказать, что тест сейчас падает и для этого используется статус xfail, но assert такие нюансы не умеет выражать.

R> может кто улучит или дополнит


Кстати, о стиле: криво расставлены forward и ссылки.
Выглядит забавно, что сначала у аргументов старательно сохраняются типы при рекурсивных вызовах, но только чтобы в функции cmp их проигнорировать в строках
R>    return val(arr[idx]);
R>    else    return arr[idx] == val;

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

Но вот выше в условии if constexpr (!std::is_integral<Arg>::value) эта проблема с неправильными ссылочными типами может влиять куда сильнее. Из-за неё вызовы заходят в разные ветки и начинаются проблемы:
    int b[1]={23};
    const int j = 23;
    assert(matches(std::span(b), 23));           //          компилируется
    assert(matches(std::span(b), j));            //       не компилируется
    assert(matches(std::span(b), std::move(j))); //    снова компилируется
Отредактировано 11.01.2024 18:06 watchmaker . Предыдущая версия . Еще …
Отредактировано 13.11.2023 23:26 watchmaker . Предыдущая версия .
Отредактировано 13.11.2023 16:15 watchmaker . Предыдущая версия .
Отредактировано 13.11.2023 16:15 watchmaker . Предыдущая версия .
Re: c++ matches like rust
От: Кодт Россия  
Дата: 14.11.23 10:45
Оценка:
Здравствуйте, reversecode, Вы писали:

R>перевожу на С++ код с раста

R>там есть прикольный макрос matches
R>накидал по быстрому более менее похожее

я конечно не знаток раста, но кажется, тамошний макрос делает несколько иное:
https://doc.rust-lang.org/std/macro.matches.html

и тогда он переводится на плюсы довольно тривиально:
#define FWD(x) (std::forward<decltype(x)>(x))

bool matches(const auto& expr, auto&&... patterns) {
  return ((expr == FWD(patterns)) || ...);
}


Всё, что нужно — это оператор равенства.

Но если его нет, то можно обмазать:
template<class Fun>
struct MatchToFun {
  Fun fun;
  friend bool operator == (const MatchToFun& p, const auto& x) { return p.fun(x); }
  friend bool operator == (const auto& x, const MatchToFun& p) { return p.fun(x); }
};

auto called(auto&& fun) {
  return MatchToFun{FWD(fun)};
}

struct Ignored {
  friend bool operator == (const Ignored&, const auto&) { return false; }
  friend bool operator == (const auto&, const Ignored&) { return false; }
};

auto ignored(auto&&) {
  return Ignored{};
}


А сделать после этого условную проверку — вообще не вопрос!
auto optionally(auto&& pattern, auto&& condfun) {
  return called([&](const auto& x) { return condfun() && x == pattern; });
}


https://gcc.godbolt.org/z/qGdWbfahP

#define SHOW(expr) (std::cout << #expr << " = " << (expr) << std::endl)

int main() {
    SHOW(matches(10, 1, 2, 3));
    SHOW(matches(10));
    SHOW(matches(10, 1, 2, 10, 3));

    auto will_crash = [](auto) { assert(false); return false; };

    SHOW(
        matches(
            10,
            ignored("ahaha"),  // даже не пробовали компилировать
            optionally(called(will_crash), []() { return false; }),  // условие не выполнено
            called([](int x) { return x == 10;}),
            called(will_crash) // а досюда не дошли
        )
    );
}
Перекуём баги на фичи!
Re[2]: c++ matches like rust
От: Кодт Россия  
Дата: 14.11.23 11:02
Оценка:
Если хочется меньшей писанины (called(...)), то можно вот так:

bool test(const auto& x, auto&& p) requires requires{x==p;} { return x==p; }
bool test(const auto& x, auto&& p) requires requires{p(x);} { return p(x); }

bool matches(const auto& expr, auto&&... patterns) {
  return (test(expr, FWD(patterns)) || ...);
}

auto ignored(auto&&) {
  return [](auto&&) { return false; };
}

auto optionally(auto&& pattern, auto&& condfun) {
    return [&](const auto& x) { return condfun() && test(x, pattern); };
}


.....
        matches(
            10,
            ignored("ahaha"),
            123,
            optionally(will_crash, []() { return false; }),
            [](int x) { return x == 10;},
            will_crash
        )
.....


Пожалуй, так даже изящнее.

https://gcc.godbolt.org/z/aaW5eG7ah
Перекуём баги на фичи!
Re: c++ matches like rust
От: Кодт Россия  
Дата: 14.11.23 11:25
Оценка:
R>но для этого нужно было делать больше перегрузок для span и обычного массива
R>иначе размерность не достать
R>а делать на одном span, нельзя в чистую захватить обычный массив

Можно же в терминах итераторов.
bool matches_its(auto begin, auto end, auto&&... patterns) {
  return
    (
    ((begin != end) && test(*begin++, FWD(patterns)))
    && ...
    )
  && (begin == end);
}

bool matches_arr(const auto& arr, auto&&... patterns) {
  return matches_its(std::begin(arr), std::end(arr), FWD(patterns)...);
}


проверяем — работает!
    SHOW(matches_arr("abc", 'a', 'b', 'z', 0));
    SHOW(matches_arr("abc", 'a', 'b'));
    SHOW(matches_arr("abc", 'a', 'b', 'c', 0, 'd'));
    SHOW(matches_arr("abc", 'a', 'b', 'c', 0));
    SHOW(matches_arr(std::initializer_list<char>{'a', 'b'}, 'a', 'b'));


Ну и всё вместе, включая ложноположительные и ложноотрицательные заглушки
https://gcc.godbolt.org/z/eWMP9s9f8
Перекуём баги на фичи!
Re[2]: c++ matches like rust
От: Кодт Россия  
Дата: 14.11.23 11:28
Оценка:
К>Можно же в терминах итераторов.

Наконец, можно в терминах кортежей. (Лень сшивать-расшивать кортежи в вариадики, сами пишите кому не жалко)

Для итераторов произвольного доступа (и, в принципе, даже для однонаправленных) ещё можно делать предварительную проверку на длину образца: это даст небольшой плюс к производительности.
bool matches_its(auto begin, auto end, auto&&... patterns) {
  return
  std::distance(begin, end) == sizeof...(patterns) &&
    (
    ((begin != end) && test(*begin++, FWD(patterns)))
    && ...
    )
  && (begin == end);
}
Перекуём баги на фичи!
Re[2]: c++ matches like rust
От: flаt  
Дата: 14.11.23 12:21
Оценка: 7 (1)
Здравствуйте, Кодт, Вы писали:


К>я конечно не знаток раста, но кажется, тамошний макрос делает несколько иное:

К>https://doc.rust-lang.org/std/macro.matches.html

Скорее, делает несколько многое.

matches! — обёртка над match — pattern matching в Rust (https://doc.rust-lang.org/book/ch18-00-patterns.html).

Сам макрос довольно тривиален, а вот match — наоборот.

Для С++ из наиболее вменяемых эмуляций есть https://github.com/BowenFu/matchit.cpp, например.
Практически всё из Rust там есть: https://github.com/BowenFu/matchit.cpp/blob/main/From-Rust-to-matchit.md

Там же разбирают примеры и с https://wg21.link/P1371: https://github.com/BowenFu/matchit.cpp/blob/main/From-Pattern-Matching-Proposal-to-matchit.md
Re[2]: c++ matches like rust
От: ArtDenis Россия  
Дата: 14.11.23 13:40
Оценка:
Здравствуйте, Кодт, Вы писали:

К>я конечно не знаток раста, но кажется, тамошний макрос делает несколько иное:

К>https://doc.rust-lang.org/std/macro.matches.html

Он применяется, когда нужен простейший паттерн-матчинг (для проверки, что вообще сматчится шаблон или нет). Для того же самого можно использовать полноценный оператор matсh или if let, но придётся написать немного больше кода.

Лично я использую этот макрос очень редко. И мне не нравится как выглядит условие с этим макросом:
if matches!(some_var, SomePattern::A(_)) {
   // do something
}

Не нравится потому что глаз цепляется за знак ! и когда смотришь код невнимательно, иногда начинает казаться, что в условии стоит отрицание
Но это тут уже оффтопик )
[ 🎯 Дартс-лига Уфы | 🌙 Программа для сложения астрофото ]
Отредактировано 14.11.2023 13:41 ArtDenis . Предыдущая версия .
Re[3]: c++ matches like rust
От: reversecode google
Дата: 16.11.23 11:57
Оценка:
классная ссылка на репу по плюсам матчес
я тоже подумывал про матч тоже, почитывая и поглядывая на попрозл
но выбрал приоритетнее то что делаю
чем тратить время на изобритение которое оказывается существует

к слову
паттен матчинг в плюсах раньше С++29 можно не ждать
на реддите выкатили консерватив/оптимистик тайм
Re[3]: c++ matches like rust
От: Кодт Россия  
Дата: 16.11.23 13:32
Оценка:
Здравствуйте, flаt, Вы писали:

F>Для С++ из наиболее вменяемых эмуляций есть https://github.com/BowenFu/matchit.cpp, например.


Чот повеяло старым бустом — не помню, где там лютовали с синтаксическим сахаром, в phoenix?..
Перекуём баги на фичи!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.