2016-04-09 37 views
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's origin 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...)); 
    } 
} 

这是我能做的最好的(如果我甚至可以做到这一点)?

回答

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

+0

感谢啊,这看起来像解决方案。这是一个耻辱,因为我不喜欢这样一个事实,即我可以在没有概念错误的情况下编写两个不可调用的对象。无论如何,我可以提供一个不需要参数类型的回调的Callable变体吗? –

+1

@ SamKellett不开箱即用。你可以选择遵守一个协议,在这个协议中,函数对象必须使它们的'签名'或其他东西接近它,但这是一个巨大的任务,没有明显的好处。 –

+0

公平,谢谢你的回答 –

相关问题