【问题标题】:Composable monadic lambda/std::function with std::optional可组合的单子 lambda/std::function 与 std::optional
【发布时间】:2020-07-14 20:07:30
【问题描述】:

我正在尝试构建一个 Filter 类型,它可以充当可组合的错误处理可调用对象(即带有 >= 的 Maybe monad),它(理想情况下)适用于(至少)lambda 和 @ C++17 中的 987654323@(无需与旧标准兼容)。理想情况下,可以这样工作:

// 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,并且它可以处理任意数量的组合函数(可能返回也可能不返回可选)。

到目前为止,我的实现(受this post 启发)在组合两个简单函数时适用于std::function,但在组合多个更改返回类型是否可选的函数时经常失败,因为类型推导非常脆弱。

#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 &lt;&lt; g 可以工作,但 f &lt;&lt; g &lt;&lt; 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&lt;T&gt;(...)T(...) 函数?还是有一种根本不同的方法来解决这个问题?

【问题讨论】:

  • auto operator&lt;&lt;(Filter&lt;TOReturn, TOArgs...&gt; other) const -&gt; Filter&lt;TReturn, std::optional&lt;TOArgs&gt;...&gt; { --> TReturn 是不是打错字了?
  • @KorelK 否 - f(g(...) 的返回类型是 f 的返回类型,即 TReturn。所以,f(g(...)) 是来自TOArgs -&gt; TReturn 的函数
  • 对不起,错过了类上面的类型。
  • 你想让h(1.0, 2.0) 是什么意思? g 是一元的,但您要向它传递两个参数……然后 f 是二进制的,您要向它传递一个参数。这只是倒退吗?
  • @巴里。抱歉 - 我在提供示例时得到了 f 的顺序并且不正确(注意:在提供的示例代码中它是正确的)。我已经更新了问题,很抱歉造成混淆!

标签: c++ templates c++17


【解决方案1】:

不确定是否理解您的代码,但...在我看来问题出在本节(删除了 cmets 并稍微简化了一点)

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

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

gresult是什么类型?

我想您希望这是某种类型的 std::optional,因此您测试是否有效 (if (gresult)) 并将其传递给以下 eval_()

但是结合g()h() 会发生什么?并结合f()?

如果我理解正确,h() 会在之前执行并返回一个int;所以gresultint

当你检查时

if (gresult)

您不检查gresult 是否有效,而是检查gresult 是否为零。

但这有效(我的意思是...编译)并且整数值用于调用等待int constg()

但是当g()的结果

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

(即std::optional&lt;int&gt;)用于调用f()(等待int),你有这个

if (gresult)

正确检查gresult是否有效,但是当你调用f()

   return this->eval_(gresult);

你传递给f() std::optional&lt;int&gt;(),而不是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>> );

bc 都是 std::optional&lt;int&gt;

我的意思是...使用std::optional 的推导指南,如果参数是std::optional,则结果类型相同。

我想这应该对你有用,但我不确定你到底想要什么。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-14
    • 2019-04-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多