模板化的require-expression [英] Templated requires-expression

查看:76
本文介绍了模板化的require-expression的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I want a Functor concept in C++20.

函子是一种可以映射的较高类型的函数。一个简单的例子是 std :: optional ;。并具有 A 类型和 B 类型的函数以及 std :: optional< A> ,您可以通过将函数应用于值(如果存在)并返回空的<$ c来轻松创建 std :: optional< B> $ c>可选否则。此操作在Haskell中称为 fmap

A functor is a higher-kinded type that can be mapped over. A simple example is std::optional; with a function from type A to type B and a std::optional<A>, you can easily create a std::optional<B> by applying the function to the value if it exists and returning an empty optional otherwise. This operation is called fmap in Haskell.

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中使用它):

A concept for all functors is simple enough to write. I've come up with this (using GCC—you'll have to remove bool to get this working in Clang, I think):

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

还有一个简单的附加功能可确保此功能有效:

And a simple additional function to make sure this works:

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 的签名,如下所示:

It works. But it's not pretty. What I want is for the signature of replace to read like so:

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

这里不需要需求子句。好多了,你不同意吗?但是,要使其正常工作,我必须将概念模板简化为一个参数。这样的事情:

No need for a requires-clause here. Much nicer, don't you agree? To get this to work, however, I'd have to reduce the template on my concept to a single argument. Something like this:

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。据我所知,在必须使用它们之前,我无处可声明它们。我想要的东西可以做到吗,并且可以简单而优雅地完成?

Problem is, I've not declared types A and B. As far as I can tell, there's nowhere I can declare them before I have to use them. Can what I want be done, and can it be done simply and elegantly?

我想到的一个可能的解决方案是在概念a中使需求表达成为可能。模板(或至少是类似模板的东西)。然后,我会有这样的东西:

One possible solution that comes to my mind is to make the requires-expression in the concept a template (or at least a template-like thing). I'd then have something like this:

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标准下无效,并且不能使用<$ c进行编译$ c> g ++-8 。这样的事情可行吗?

Unfortunately, this is not valid by the C++20 standard and will not compile with g++-8. Could something like this be viable? Could it make it into the standard?

推荐答案

C ++没有这样的参数多态性-您不能做类似的事情?在Haskell中以您想要的方式适用于任何类型。我认为,在存在超载的世界中,这根本是不可能的。

C++ doesn't have parametric polymorphism like this - you can't do things like "for any type" in the way you want to, and in the way you can in Haskell. I think that's fundamentally impossible in a world where overloading exists.

您所拥有的是以下内容(我继续并删除了错误的 bool , C ++ 20概念,并修复了->类型,该类型也已删除):

What you have is the following (I went ahead and removed the erroneous bool, which isn't part of C++20 concepts, and fixed -> Type, which was also removed):

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

您要说的是任何类型的 a b ,并给出 a-> b ,您可以调用此函数。我们不能那样做。但是我们可以自己选择任意类型。做到这一点的一种方法是选择函子实现不知道的秘密类型:

What you want to say is for any types a and b, given an a -> b, you can call this function. We can't do that. But we can just pick arbitrary types ourselves. One way of doing that is to pick secret types that the functor implementation just wouldn't know about:

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 对,以使其更正确。

That's probably your best bet. You could even add multiple a/b pairs to make this more likely to be correct.

无论如何,

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

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

Isn't going to happen anyway, since we don't have that kind of terse syntax for constrained template template parameters. You'd have to write it this way:

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






这是一个不好的提示 fmap 的实现,用于可选

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

使用 std :: function< Sig> 表示,如果您专门传递 std :: function ,这将仅起作用。不适用于lambda或函数指针或其他函数对象(例如我先前使用的 secret :: F )。而且即使它确实起作用了,您也还是不想这样做,因为这是不必要的开销。

Taking a std::function<Sig> means this will only work if you pass in specifically a std::function. Not for lambdas or function pointers or other function objects (like the secret::F I used earlier). And even if it did work, you wouldn't want to do this anyway, since it's needless overhead.

您想要:

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

我有一整篇关于这个确切问题的帖子,使用概念的声明

I have a whole post about this exact problem, Declarations Using Concepts.

这篇关于模板化的require-expression的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆