Составной монади c лямбда / std :: function с std :: optional - PullRequest
3 голосов
/ 14 июля 2020
• 1000 1002 * в C ++ 17 (нет необходимости в совместимости со старыми стандартами). В идеале, что-то, что работало бы так:
// two example Filter's instantiated from lambda's
Filter g = [](const int a, const int b) -> float {
                  return a + b; 
           };
Filter f = [](const float c) -> std::optional<int> {
              if c < 0 return std::nullopt;
              else return c + 1;
           };

// compose them - only two right now for brevity
auto h = f << g; // so that this implements f(g(...))

// and then later call them with some arguments
h(1.0, 2.0);

Если какая-либо из промежуточных функций возвращает std::nullopt, мне нужно, чтобы вся композиция возвращала std::nullopt и чтобы это работало с любым количеством составных функций (которые могут возвращать или не возвращать опции).

Реализация, которую я до сих пор (вдохновленная этим сообщением ), работает для std::function при составлении двух простых функций, но регулярно дает сбой при объединении несколько функций, которые меняют, являются ли возвращаемые типы необязательными или нет, поскольку вывод типа чрезвычайно велик fr agile.

#include <functional>
#include <optional>
#include <type_traits>

template <typename TReturn, typename... TArgs>
class Filter {

  public:

  /**
   * The filter function that we evaluate.
   */
  std::function<TReturn(TArgs...)> eval_;

  /**
   * Construct a filter from a std::function.
   */
  Filter(std::function<TReturn(TArgs...)> f) : eval_(f) {}

  /**
   * Apply the filter to given arguments.
   */
  auto operator()(TArgs... args) const {
    return this->eval_(args...);
  }

  /**
    * Compose this function, `f`, and `g`.
    */
  template <typename TOReturn, typename... TOArgs>
  auto operator<<(Filter<TOReturn, TOArgs...> other) const -> Filter<TReturn, std::optional<TOArgs>...> {

    // the result of the resulting function f(g(...))
    using TFReturn = std::optional<typename TReturn::value_type>;

    // the type of the resulting function f(g(...))
    using TFuncType = std::function<TFReturn(std::optional<TOArgs>...)>;

    // construct (and return) the composed function
    TFuncType f = [this, other](std::optional<TOArgs>... args) -> TFReturn {

                    // if we got a good value, perform the function composition
                    if ((args && ...)) {

                      // evaluate g over the input arguments
                      auto gresult = other(*(args)...);

                      // if we got a non-fail result from g, then call f
                      if (gresult) {

                        // and evaluate our own function over the result
                        return this->eval_(gresult);

                      }

                    } // END: if ((args && ...))

                    // if anything falls through, return failur
                    return std::nullopt;

                  };

    return f;

    } // END: operator<<


}; // END: class Filter


auto main() -> int {

  // a couple of test functions
  std::function<int(float, float)> f1 = [](const float a, const float b) -> int {
             return a * b;
           };

  std::function<std::optional<int>(int)> f2 = [](const int c) -> std::optional<int> {
               if (c < 0) return std::nullopt;
               else return c;
             };

  std::function<std::optional<int>(int)> f3 = [](const int d) -> std::optional<int> {
               if (d > 10) return 10;
               else return d;
             };

  // these construct fine if I have explicitly typed std::function's above.
  Filter f = f3;
  Filter g = f2;
  Filter h = f1;

  // build the composition
  auto F = g << h; // this works!
  // auto F = f << g << h; // this does not work

  // evaluate our composition over different arguments
  auto x = F(1.0, 2.0);
  auto y = F(-1.0, 2.0);

}


Составление двух простых функций, f << g, работает, но f << g << h в настоящее время не работает с следующая ошибка:

filter.cpp: In instantiation of ‘Filter<TReturn, std::optional<TOArgs>...> Filter<TReturn, TArgs>::operator<<(Filter<TOReturn, TOArgs ...>) const [with TOReturn = std::optional<int>; TOArgs = {int}; TReturn = std::optional<int>; TArgs = {int}]’:
filter.cpp:98:17:   required from here
filter.cpp:55:43: error: no match for call to ‘(const std::function<std::optional<int>(int)>) (std::optional<int>&)’   55 |                         return this->eval_(gresult);      |                                ~~~~~~~~~~~^~~~~~~~~
In file included from /usr/include/c++/10/functional:59,                 from filter.cpp:1:
/usr/include/c++/10/bits/std_function.h:617:5: note: candidate: ‘_Res std::function<_Res(_ArgTypes ...)>::operator()(_ArgTypes ...) const [with _Res = std::optional<int>; _ArgTypes = {int}]’  617 |     function<_Res(_ArgTypes...)>::      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/include/c++/10/bits/std_function.h:618:25: note:   no known conversion for argument 1 from ‘std::optional<int>’ to ‘int’  618 |     operator()(_ArgTypes... __args) const      |                ~~~~~~~~~^~~~~~~~~~
/usr/include/c++/10/bits/std_function.h:601:7: error: ‘std::function<_Res(_ArgTypes ...)>::function(_Functor) [with _Functor = Filter<TReturn, TArgs>::operator<< <std::optional<int>, {int}>::<lambda(std::optional<int>)>; <template-parameter-2-2> = void; <template-parameter-2-3> = void; _Res = std::optional<int>; _ArgTypes = {std::optional<int>}]’, declared using local type ‘Filter<TReturn, TArgs>::operator<< <std::optional<int>, {int}>::<lambda(std::optional<int>)>’, is used but never defined [-fpermissive]  601 |       function<_Res(_ArgTypes...)>::

Как я могу изменить вывод типа для поддержки функций std::optional<T>(...) и T(...) в произвольных композициях? Или есть принципиально другой подход к этому решению?

1 Ответ

0 голосов
/ 14 июля 2020

Не уверен, что понимаю ваш код, но ... мне кажется, что проблема в этом разделе (удалены комментарии и немного упрощено)

auto gresult = other(*(args)...);

if (gresult)
   return this->eval_(gresult);

Какой тип gresult?

Я полагаю, вы ожидаете, что это std::optional определенного типа, поэтому вы проверяете, действительно ли это (if (gresult)), и передаете его следующему eval_().

Но что происходит при объединении g() и h()? И объединение также f()?

Если я правильно понимаю, h() выполняется раньше и возвращает int; поэтому gresult - это int.

Когда вы проверяете

if (gresult)

, вы не проверяете, действительно ли gresult, но если gresult равен нулю или нет.

Но это работает (я имею в виду ... компилировать), и целочисленное значение используется для вызова g(), который ждет int const.

Но когда результат g()

auto gresult = other(*(args)...);

(то есть std::optional<int>) используется для вызова f() (который ждет int), у вас это

if (gresult)

правильно проверьте, gresult действительно, но когда вы звоните f()

   return this->eval_(gresult);

, вы переходите к f() a std::optional<int>(), а не к int.

Не уверен в решении ( не уверен, что именно вы хотите, и насчет руководств по вычету std::option), но мне кажется, что вы должны наложить, что gresult - это std::option какого-то типа

std::option  gresult{ other( args.value()... ) };

, поэтому следующая проверка

if ( gresult )

всегда имеет значение gresult, затем вы должны передать значение из gresult следующей функции

return this->eval_( gresult.value() );

Обратите внимание, что в следующем коде

int a;

std::optional  b{a};
std::optional  c{b};

static_assert( std::is_same_v<decltype(b), std::optional<int>> );
static_assert( std::is_same_v<decltype(c), std::optional<int>> );

и b, и c равны std::optional<int>

Я имею в виду ... используя инструкции по выводу для std::optional, если аргумент - std::optional, результирующий тип будет таким же.

Я полагаю что это должно сработать для вас, но я не уверен, что именно вы хотите.

...