2

所以我在玩 GCC6 及其概念实现,我认为 Haskell Prelude 将是一个很好的实验来源。Haskell 的核心特性之一是函数组合,这是我需要立即解决的问题。

尽我所能模仿 Haskell 语法,我写了这个函数:

template <typename F, typename G>
auto operator*(F f, G g)
{
  return [f, g](auto... args) {
    return f(g(args...));
  }
}

效果很好,让我可以做以下事情:

auto add([](int a, int b) { return a + b; }
auto doubled([](int a) { return a * 2; }

auto add_then_double(doubled * add);
assert(add_then_double(2, 3) == 10);

很高兴,我决定回去对我的函数组合应用一些约束,但由于它的懒惰,我很快遇到了一个问题。

首先我写了这个概念:

template <typename F, typename Ret, typename... Args>
concept bool Function()
{
  return requires(F f, Args ...args) {
    { f(args...) } -> Ret;
  }
}

我基于Andrew Sutton 的原始github 项目中的概念。

所以我试图应用到我原来的功能。我遇到的问题是我不知道G返回什么而不知道传递给什么参数,G所以我无法约束G,我不知道F返回什么而不知道它给出了什么参数,我不知道,因为我不知道G返回什么。

我很确定我需要一个Function不关心返回类型的新概念,因为我的组合函数不关心F返回什么,只要它是可调用的。而且我想我可以对内部 lambda 进行约束,即参数类型并更正 G 和 F 但这意味着我可以编写不可组合的函数并且在调用站点之前不会出错。这是可以避免的吗?

也许是这样的:

template <typename F, typename G>
auto operator*(F f, G g)
{
  return [f, g](auto... args) 
    // is it even possible to constrain here?
    requires FunctionAnyReturn<G, decltype(args)...>
      && FunctionAnyReturn<F, decltype(G(decltype(args)...))>
  {
    return f(g(args...));
  }
}

这是我能做的最好的吗(如果我能做到的话)?

4

1 回答 1

1

正如您所发现的,将约束放在正确的位置确实很重要。在您的情况下operator(),必须限制的是结果,而不是组合函数本身。你真的不能做得更好,例如考虑许多函数没有单一的返回类型(例如std::make_tuple)。然而,尽管 Concepts-Lite 确实稍微涉及了 lambda 表达式,但它并没有允许requires在它们上使用子句,因此您的尝试将行不通。

在大多数情况下,我通常的建议是编写 lambda 表达式,使得结果operator()自然受到 SFINAE 的约束。在您的情况下,这意味着避免返回类型扣除:

return [f, g](auto... args) -> decltype( f(g(args...)) )
{ return f(g(args...)); }

如果你使用例如 Clang,一切都是桃色的。如果使用 GCC,您可能会遇到GCC 过早执行某些检查的错误。

另一种方法是竭尽全力“展开” lambda 表达式的闭包类型。通过使其成为用户定义的类型,您可以获得所有技巧,特别是您可以编写所需的显式约束:

template<typename F, typename G>
struct compose_type {
    F first_composed_function;
    G second_composed_function;

    template<typename... Args>
    constexpr auto operator()(Args... args)
        // substitute in whichever concepts and traits you're actually using
        requires
            Callable<G, Args...>
            && Callable<F, result_of<G, Args...>>
    { return first_composed_function(second_composed_function(args...)); }
};

template<typename F, typename G>
constexpr compose_type<F, G> compose(F f, G g)
{ return { std::move(f), std::move(g) }; }

Live On Coliru

于 2016-04-10T19:41:46.893 回答