【问题标题】:Templated requires-expression模板化需求表达式
【发布时间】:2020-03-05 14:24:22
【问题描述】:

我想要 C++20 中的 Functor 概念。

函子是一种可以被映射的更高种类的类型。一个简单的例子是std::optional;使用从A 类型到B 类型和std::optional<A> 的函数,您可以轻松地创建std::optional<B>,方法是将该函数应用于该值(如果存在),否则返回一个空的optional。这个操作在 Haskell 中称为fmap

template<typename A, typename B>
std::optional<B> fmap(std::function<B(A)> f, std::optional<A> fa) {
    if (!fa) {
        return std::optional<B>{};
    }
    return std::optional<B>(f(*fa));
}

一个适用于所有函子的概念很容易编写。我想出了这个(使用 GCC——你必须删除 bool 才能让它在 Clang 中工作,我认为):

template<template<typename> typename F, typename A, typename B>
concept bool Functor = requires(std::function<B(A)> f, F<A> fa) {
    { fmap(f, fa) } -> F<B>;
};

还有一个简单的附加功能来确保它有效:

template<typename A, typename B>
std::function<B(A)> constant(B b) {
    return [b](A _) { return b; };
}

template<template<typename> typename F, typename A, typename B>
F<B> replace(B b, F<A> fa) requires Functor<F,A,B> {
    return fmap(constant<A,B>(b), fa);
}

它有效。但这并不漂亮。我想要的是replace 的签名是这样的:

template<Functor F, typename A, typename B>
F<B> replace(B b, F<A> fa);

这里不需要 requires 子句。好多了,你不同意吗?然而,要让它发挥作用,我必须将我的概念模板简化为一个参数。像这样的:

template<template<typename> typename F>
concept bool Functor = requires(function<B(A)> f, F<A> fa) {    // Uh-oh
    { fmap(f, fa) } -> F<B>;
};

问题是,我没有声明类型 A 和 B。据我所知,在我必须使用它们之前,我无法声明它们。我想做的事能做到吗,能不能简单优雅地完成?

我想到的一个可能的解决方案是使概念中的 requires-expression 成为模板(或至少是类似模板的东西)。然后我会有这样的事情:

template<template<typename> typename F>
concept bool Functor = requires<typename A, typename B>(function<B(A)> f, F<A> fa) {
    { fmap(f, fa) } -> F<B>;
};

很遗憾,这在 C++20 标准中是无效的,并且无法使用 g++-8 进行编译。这样的事情是否可行?能不能成为标准?

【问题讨论】:

  • @NicolBolas C++ 没有这样的概念。有些人在表示“函数对象”时会使用“函子”这个词。 functor 和 monad 是不同的,但相关的东西。
  • @Barry:“C++ 没有这样的概念。”是的,有。只需谷歌“C++ 函子”,您就会发现数以千计的匹配项,其中没有一项符合 Haskell 的定义。它可能没有被标准定义(这就是我在“概念”周围使用引号的原因),但它是 C++ 社区中的一个艺术术语,具有非常具体的定义。
  • @NicolBolas “它可能没有被标准定义”——事实上,这使它成为一个没有意义的艺术术语,当我们有一个完全合理的替代品时,我们应该鼓励人们停止使用它“功能对象。”特别是因为函子在 FP 世界中具有更有用和更具体的含义。
  • @Barry:“这使它成为一个没有意义的艺术术语” 整个 C++ 社区都不同意,所以我会接受他们的评估。如果你想与人交流,你必须使用他们理解的术语,而不是试图强迫他们遵循你的定义。 “特别是因为函子在 FP 世界中具有更有用和更具体的含义。” C++ 不是函数式语言,因此它不需要遵循函数式命名约定。
  • @Barry:人们知道“函数对象”是什么意思。但 C++ 程序员也认为“函子”是“函数对象”的缩写。如果您开始与他们交谈,就好像这些是不同的术语,他们将无法理解您在说什么。不同的社区用词不同;最好接受这一点并继续前进。

标签: c++ templates c++20 c++-concepts


【解决方案1】:

C++ 没有像这样的参数多态性——你不能以你想要的方式和在 Haskell 中那样做诸如“任何类型”之类的事情。我认为在一个存在超载的世界中,这基本上是不可能的。

您所拥有的是以下内容(我继续删除了错误的 bool,它不是 C++20 概念的一部分,并修复了 -&gt; Type,它也被删除了):

template<template<typename> class F, typename A, typename B>
concept Functor = requires(std::function<B(A)> f, F<A> fa) {
    { fmap(f, fa) } -> std::same_as<F<B>>;
};

你想说的是任何类型ab,给定一个a -&gt; b,你可以调用这个函数。我们不能那样做。但是我们可以自己选择任意类型。一种方法是选择函子实现不知道的秘密类型:

namespace secret {
    struct A { };
    struct B { };

    template <typename From, typename To>
    struct F {
        auto operator()(From) const -> To;
    };
}

template <template <typename> class F>
concept Functor = requires(secret::F<secret::A, secret::B> f, F<secret::A> fa) {
    { fmap(f, fa) } -> std::same_as<F<secret::B>>;
};

这可能是你最好的选择。您甚至可以添加多个 a/b 对,以使其更可能是正确的。

不管怎样,这个:

template<Functor F, typename A, typename B>
F<B> replace(B b, F<A> fa);

无论如何都不会发生,因为我们没有那种用于受约束模板模板参数的简洁语法。你必须这样写:

template <template <typename> class F, typename A, typename B>
    requires Functor<F>
F<B> replace(B b, F<A> fa);

附带说明一下,对于optional,这是一个糟糕的fmap 实现:

template<typename A, typename B>
std::optional<B> fmap(std::function<B(A)> f, std::optional<A> fa);

采用std::function&lt;Sig&gt; 意味着这只有在您传入特别 std::function 时才有效。不适用于 lambda 或函数指针或其他函数对象(如我之前使用的 secret::F)。即使它确实有效,你也不想这样做,因为它是不必要的开销。

你想要:

template <typename F, typename A, typename B = std::invoke_result_t<F&, A const&>>
std::optional<B> fmap(F f, std::optional<A> fa);

我有一篇关于这个确切问题的完整帖子,Declarations Using Concepts

【讨论】:

    【解决方案2】:

    您的“Functor”概念表示三种不同类型之间的复杂关系:F(被投影的单参数模板)、A(起始对象类型)和@987654324 @(生成的对象类型)。您的概念表示 3 个参数之间的关系,因此您的概念将必须采用 3 个参数。

    简洁的模板语法适用于简单的情况:与单个(类型)参数相关的约束。你的情况并不简单,所以你必须用requires 子句把它拼出来。任何时候,只要你有一个包含多个参数的概念,你就必须把它拼出来。

    至于“漂亮”与否,这是一个价值判断。但是考虑到这里展示的复杂关系,看到它的拼写就可以清楚地知道所有这些参数之间的关系是什么。清晰有它自己的美。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-12-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多