LINUX.ORG.RU

Логика внутри fold-expression

 , fold-expressions


0

2

У меня есть набор статически определенных объектов, каждый из которых надо вызвать, до тех пор пока код возврата нормальный, и прекратить вызовы объектов, как только очередной код возврата не нормальный.

Допустим, объекты - лямбды. Вот, что у меня получилось:

#include <iostream>

template <class... Ts>
int Returns(Ts&&... args)
{
    int i = 0;
    int tmp;
    (((tmp = args()) && (i = tmp)) || ...);
    return i;
}

int main()
{
    std::cout << Returns([](){return 0;}, [](){return 9;}, [](){return 0;}) << std::endl;
}

Это работает, но возможен ли другой, более компактный вариант без временной переменной и дополнительный шаблонов, вызывающих отдельно каждый из объектов или чего-то подобного вспомогательного, как обычно делается в старых плюсах?

Еще есть одна проблема, по крайней мере с GCC (-Wall) - компилятор показывает предупреждение:

main.cpp: In instantiation of 'int Returns(Ts&& ...) [with Ts = {main()::<lambda()>, main()::<lambda()>, main()::<lambda()>}]':

main.cpp:14:75:   required from here

main.cpp:8:22: warning: suggest parentheses around '&&' within '||' [-Wparentheses]

    8 |     (((tmp = args()) && (i = tmp)) || ...);

      |      ~~~~~~~~~~~~~~~~^~~~~~~~~~~~~

main.cpp:8:22: warning: suggest parentheses around '&&' within '||' [-Wparentheses]
★★★★★

template<typename&& T, typename&&... Args>
int Returns(T&& first, Args&&... args) {
  return first() + Returns(args...);
}

Что-то вроде этого должно получиться.

xpahos ★★★★★
()
Последнее исправление: xpahos (всего исправлений: 1)
#include <functional>
#include <iostream>
#include <type_traits>

template <typename ... Invocables>
void invoke_all (Invocables&& ... invs) {
    ((std::invoke (invs)), ...);
}

int main() {
    invoke_all([](){ std::cout << "Hello"; }, [](){ std::cout << "World"; });
    return 0;
}
~

Siborgium ★★★★★
()

Или так можно. А можно завернуть через perfect forwarding в тупл и через std::apply извращаться.

#include <functional>
#include <iostream>
#include <type_traits>

template <typename Head, typename ... Args>
struct head { using type = Head; };

template <typename ... Args>
using head_t = typename head<Args...>::type;

template <typename ... Invocables>
auto invoke_all (Invocables&& ... invs) -> typename std::invoke_result_t<head_t<Invocables...>> {
    return ((std::invoke (invs)) + ...);
}

int main() {
    auto a = invoke_all ([](){ return 40; }, [](){ return 2; });
    std::cout << a;
    return 0;
}

Siborgium ★★★★★
()

Я, возможно, непонятно выразился, но нужно прекращать итерацию/рекурсию при выполнении условия, и из-за этого весь траходром с временной переменной. К тому же, во всех случаях с классической рекурсивной обработкой параметр пака а ля С++11 необходима отдельная перегрузка функции для терминальной итерации, чем fold-expression как раз и отличается от явной рекурсии - не нужны дополнительные перегрузки.

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

Короче, нужна была простая (не бурьяноподобная) реализация чего-то типа std::find_if, но для статически определяемых объектов. Вобщем, остановлюсь на моем варианте. Возможно это баг в GCC. Clang вроде не жалуется.

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

Так, пожалуй, более читаемо, и без варнинга.

seiken ★★★★★
() автор топика

Ты пытался родить это?:

template<typename ... Ts> auto returns(Ts && ... args) {
  int res;
  (void)(!(res = args()) && ...);
  return res;
}

Я просто не понимаю что ты пытался написать.

anonymous
()
Ответ на: комментарий от eao197
const auto & call = [&i](auto && what) {
        return 0 == (i = what());
    };

const auto & call
auto && what
0 == (i = what())

Уровень состоятельности как всегда - нулевой. Не позорь кресты своим существованием.

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

Не фанат однострочников написал бы что-то такое:

template <typename T, typename... Args>
int returns(T&& first, Args&&... tail)
{
    if(int r=first())
        return r;

    if constexpr(sizeof...(Args))
        return returns(std::forward<Args>(tail)...);

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

В вариант автора можно добавить ещё и запятую. Хотя сейчас я посмотрел - да, автор делал через ||. Т.е. твоё ближе к тому, что автор хотел написать. Я почему-то зацепился за &&. Сторона для ... тут роли не играет, но слева действительно правильней.

anonymous
()

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

template <class F>
int operator% (int res, F&& f) {
    if (res != 0)
        return res;
    else
        return f();
}

template <class... Ts>
int Returns(Ts&&... args)
{
    return (0 % ... % args);
}
anonymous
()

Можно без временной переменной

template <class... Ts>
int Returns(Ts&&... args)
{
    int i = 0;
    ((i = args()) || ...);
    return i;
}
anonymous
()
Ответ на: комментарий от anonymous

int operator% (int res, F&& f) {

За такое морду лицу бьют.

Это единственный способ для «логики внутри fold-expression» - перегрузить оператор для fold-expression. Бей морду стандартописателям.

Все остальные приведенные здесь костыли с размазанной логикой и снаружи и внутри fold-expression.

anonymous
()
template <typename... Args>
int returns(Args&&... args) {
  int status = 0;
  auto checkStatus = [&status](auto& f) {
    status = std::invoke(f);
    return status == 0;
  };

  (checkStatus(args) && ...);

  return status;
}

Можно еще форвардить в лямбду (для корректности), но мне лень…

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

Это единственный способ для «логики внутри fold-expression» - перегрузить оператор для fold-expression. Бей морду стандартописателям.

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

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

Создал бы свой тип и не ломал бы глобальный оператор.

У тебя жаба головного мозга - класс на каждый чих. С таким же успехом я мог бы спрятать оператор в неймспейс. Если уж плодить сущности, то я бы написал нормальный фолд, но эта тема именно про «кривой» fold-expression.

Набросок

template <F>
int results(F f, int res) { return res; }
template <F, X, Xs xs...>
int results(F f, res, X x, Xs... xs) {
   return results(f, f(res, x), xs...);
}
int f(res, f) {
  // тут так называемая логика внутри фолд
}
anonymous
()
Ответ на: комментарий от anonymous

У тебя жаба головного мозга

Создание отдельного класса под оператор это С++-стиль (см. те же функторы), и в Java он просто не доступен. Потому это ты скорее о своем наболевшем.

С таким же успехом я мог бы спрятать оператор в неймспейс.

И все-равно сломать семантику глобального оператора в рамках этого неймспейса (а в нем кроме оператора будет еще и код, его использующий).

Набросок

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

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

это С++-стиль (см. те же функторы) Поэтому в новых стандартах напридумывали, например, всякие лямбды, чтобы поломать этот «С++-стиль».

На остальное отвечать не буду, я не стильный.

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

это С++-стиль (см. те же функторы)

Поэтому в новых стандартах напридумывали, например, всякие лямбды, чтобы поломать этот «С++-стиль».

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

Поэтому в новых стандартах напридумывали, например, всякие лямбды, чтобы поломать этот «С++-стиль».

Не хочу тебе расстраивать, но лямбда в С++ - это сахар для функторов.

#include <iostream>
#include <type_traits>

template <typename T>
void print_is_class( T&& ) {
    std::cout << std::is_class_v<T> << std::endl;
} 

int main() {
    print_is_class([]{});
}
anonymous
()
Ответ на: комментарий от anonymous

Стиляга.

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

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

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

По теме я уже привел код, после котого ТС отметил тему как решенную. Это уже ты что-то свое хочешь.

Заодно узнаю, умеешь ли ты «логику внутри фолд», а не размазанный где ни попадя.

А это от задачи зависит и понимания, что ты имеешь ввиду. Если надо что-то более-менее универсальное, то вот, например:

#include <functional>
#include <iostream>

template <typename F, typename H, typename... Args>
auto fold(F&& f, H&& head, Args&&... tail) {
    auto r = f({}, head);
    (... , (r = f(r, tail)));
    return r;
}

int main() {
    std::cout 
    << fold(
        [](int r, std::function<int()> f) { return r ? r : f(); },
        [](){return 0;}, 
        [](){return 9;}, 
        [](){return 5;})
    << std::endl;
}
anonymous
()
Ответ на: комментарий от anonymous

Используешь кривой «сахар». Что, если список пустой `fold(f /*, ничего*/)`? Насколько нечитаемее каноническая реализация fold{l}?

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

Используешь кривой «сахар».

Ну хоть не обвинил в том, что использую кривой С++, на ЛОР-е и этого хватает, чтоб предать анафеме.

Что, если список пустой `fold(f /*, ничего*/)`?

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

Насколько нечитаемее каноническая реализация fold{l}?

Смотря что ты имеешь ввиду под канонической реализацией.

anonymous
()
Ответ на: комментарий от anonymous
template <typename F, typename H, typename... Args>
auto fold(F&& f, H&& head, Args&&... tail) {
    auto r = f({}, head);
    (... , (r = f(r, tail)));
    return r;
}

Как это вообще работает? что тут такое ...? Это вызов самой себя с параметрами - tail?

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

По теме я уже привел код, после котого ТС отметил тему как решенную.

«После» не означает, что твое решение правильное. И как докажешь, что именно после твоего сообщения, ты - админ?

Судя по стилю ТС, когда всё размазано по всему коду, его не интересует «логика внутри фолд». А решает от совсем другую задачу - find_if

Логика внутри fold-expression (комментарий)

seiken> реализация чего-то типа std::find_if, но для статически определяемых объектов. Вобщем, остановлюсь на моем варианте

Так что, «решено» своим решением. Не присваивай себе чужое.

Еще «придирка»

auto fold(F&& f, H&& head, Args&&... tail) {
    auto r = f({}, head);

И тут размазал логику по всему коду - взял начальное значение неизвестно откуда {}. Зачем он здесь нужен, если у тебя список не может быть пустым?

Канонически fold{l} принимает функцию, начальное значение и список. Если список не пустой, можно за начальное значение взять первый элемент списка (а не придумывать неизвестно что ‘{}’)

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

Так что, «решено» своим решением.

Я в итоге взял

((i = args()) || ...)
как самый минималистичный вариант.

Мне там правда еще нужно было дополнительное сравнение вставить, т.к. используемый тип данных не приводился неявно к int, но сути это не меняет.

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

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

С другой стороны, есть еще один юз-кейс

Может пора писать/использовать нормальные фолды(рекурсии) с нормальными финкциями со своей логикой? Иначе это будет похоже на write-only перловые регекспы-однострочники.

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

Может пора писать/использовать нормальные фолды(рекурсии) с нормальными финкциями со своей логикой? Иначе это будет похоже на write-only перловые регекспы-однострочники.

Да, видимо, пора.

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

В этом варианте внутри лямбда-функции можно реализовать какую-нибудь дополнительную функциональность при каждом вызове статически определенного объекта. Например, посчитать количество вызовов:

int count = 0;
int status = 0;

auto checkStatus = [&status, &count] (auto & func)
{
  status = func();
  ++count;
  return status == 0;
};

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

И как докажешь, что именно после твоего сообщения, ты - админ?
seiken> реализация чего-то типа std::find_if, но для статически определяемых объектов. Вобщем, остановлюсь на моем варианте
Так что, «решено» своим решением. Не присваивай себе чужое.

ТС уже ответил.

И тут размазал логику по всему коду - взял начальное значение неизвестно откуда {}

Добро пожаловать в шаблоны, {} задается аргументом шаблона, как и прочая семантика реализации.

Зачем он здесь нужен, если у тебя список не может быть пустым?
Канонически fold{l} принимает функцию, начальное значение и список. Если список не пустой, можно за начальное значение взять первый элемент списка (а не придумывать неизвестно что ‘{}’)

За начальное значение брать первый элемент списка нельзя, т.к. это не готовый результат, а функция, которую с помощью другой функции надо преобразовать в значение. А эта другая функция принимает на вход два аргумента. Потому реальным начальным значением в данной конкретной задаче является 0. Его можно было или добавить в параметры или инициализировать по умолчанию. Добавить в параметры было более правильным, но т.к. речь шла о конкретной задаче, я не стал заморачиваться. Эту придирку можешь засчитывать.

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

ТС уже ответил.

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

Про остальное.

Поэтому нормальный фолд должен выглядет так как должен. Чтобы логика не зависела от «инициализатора» по умолчанию, который неизвестно где и как реализован. Чтобы не писать функцию-преобразователь первого элемента списка. И чтобы не предполагать, что значние по умолчанию - это '0'. Что '0' - это то, что по логике требуется, а не INT_MAX.

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

Чтобы логика не зависела от «инициализатора» по умолчанию, который неизвестно где и как реализован.

Дефолтно инициализированное начальное значение для fold/reduce встречается в самых разных языках (в том числе в стандартной реализации С++), это банально удобно. А когда оно надо, в том же С++ добавляется перегруженная функция с дополнительным параметром.

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

На что люди не идут, чтобы Scala не учить.

И их можно понять.

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

Само собой :) Суть в том, что б логику завернуть в лямбду, и тогда… твои fold-expressions станут мягкими и шелковистыми.

По крайней мере пока не добавят чего-то нового в стандарт.

KennyMinigun ★★★★★
()
Ответ на: комментарий от anonymous
template <class F>
int operator% (int res, F&& f)

И теперь любой пользовательский тип умеет возвращать остаток от деления int на него. Или нет? WAIT, OH SH~

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

Еще один «экперт»

namespace my_best_super_puper_operators {
  int operator%(...) { ... }
}

int result(...) {
  using my_best_super_puper_operators::operator%;
  ...
}

anonymous
()
 object Main extends App
{
    for (obj <- objList if obj.returnCode == SUCCESS) 
    {
         Console.println(obj);
    }
}
LongLiveUbuntu ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.