在我知道可调用的参数之前,如何约束惰性组合?

时间:2016-04-09 13:00:57

标签: c++ generic-programming c++17 c++-concepts

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

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

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;
  }
}

很高兴,我决定回去对我的功能构成应用一些约束,但由于它的懒惰我很快遇到了问题。

首先我写了这个概念:

G

我基于Andrew Sutton's origin github项目中的概念。

所以我试着申请原来的功能。我遇到的问题是,我不知道G返回的是什么,而不知道传递给G的参数是什么,所以我不能约束F而我不会&#39} ;知道G返回的内容并不知道它给出了什么参数,我也不知道,因为我不知道Function返回的是什么。

我非常确定我需要一个不关心返回类型的新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...)); } } 返回的内容,所以只要它是可以调用的。而且我想我可以将约束放在参数键入的内部lambda上并为G校正,因此对于F,但这意味着我可以组成不可组合的函数,并且在调用站点之前不会得到错误。这是可以避免的吗?

也许是这样的:

{{1}}

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

1 个答案:

答案 0 :(得分: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,everything is peachy。如果使用GCC,您可能会遇到GCC performs some checking too early

的错误

另一种方法是尽快“展开”## 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