模板化的 requires-expression
Templated requires-expression
我想要 C++20 中的 Functor 概念。
仿函数是一种可以映射的高级类型。一个简单的例子是std::optional
;使用从类型 A
到类型 B
和类型 std::optional<A>
的函数,您可以通过将函数应用于值(如果存在)并返回空 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);
这里不需要要求子句。好多了,你不同意吗?然而,为了让它发挥作用,我必须将我的概念模板缩减为单个参数。像这样:
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
编译。这样的事情可行吗?它能成为标准吗?
您的“Functor
”概念代表三种不同类型之间的复杂关系:F
(一个单参数模板,即被投影的模板),A
(起始对象类型)和 B
(结果对象类型)。您的概念代表了 3 个参数之间的关系,因此您的概念将必须采用 3 个参数。
简洁的模板语法适用于简单的情况:与单个(类型)参数相关的约束。您的情况并不简单,因此您将不得不用 requires
子句将其拼写出来。任何时候你有一个像这样的多个参数的概念,你都必须把它拼出来。
至于是不是"pretty",那是价值判断。但鉴于此处显示的复杂关系,看到它的详细说明可以清楚地了解所有这些参数之间的关系。清晰自有其美。
C++ 没有像这样的参数多态性——你不能像 "for any type" 那样以你想要的方式做事,也不能像 Haskell 那样做。我认为这在存在超载的世界中根本不可能。
你有以下内容(我继续删除了错误的 bool
,它不是 C++20 概念的一部分,并修复了 -> 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>>;
};
你想说的是对于任何类型a
和b
,给定一个a -> 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<Sig>
意味着这 只有 如果你传入 特别地 一个 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);
关于这个确切的问题,我有一个完整的 post,Declarations Using Concepts。
我想要 C++20 中的 Functor 概念。
仿函数是一种可以映射的高级类型。一个简单的例子是std::optional
;使用从类型 A
到类型 B
和类型 std::optional<A>
的函数,您可以通过将函数应用于值(如果存在)并返回空 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);
这里不需要要求子句。好多了,你不同意吗?然而,为了让它发挥作用,我必须将我的概念模板缩减为单个参数。像这样:
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
编译。这样的事情可行吗?它能成为标准吗?
您的“Functor
”概念代表三种不同类型之间的复杂关系:F
(一个单参数模板,即被投影的模板),A
(起始对象类型)和 B
(结果对象类型)。您的概念代表了 3 个参数之间的关系,因此您的概念将必须采用 3 个参数。
简洁的模板语法适用于简单的情况:与单个(类型)参数相关的约束。您的情况并不简单,因此您将不得不用 requires
子句将其拼写出来。任何时候你有一个像这样的多个参数的概念,你都必须把它拼出来。
至于是不是"pretty",那是价值判断。但鉴于此处显示的复杂关系,看到它的详细说明可以清楚地了解所有这些参数之间的关系。清晰自有其美。
C++ 没有像这样的参数多态性——你不能像 "for any type" 那样以你想要的方式做事,也不能像 Haskell 那样做。我认为这在存在超载的世界中根本不可能。
你有以下内容(我继续删除了错误的 bool
,它不是 C++20 概念的一部分,并修复了 -> 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>>;
};
你想说的是对于任何类型a
和b
,给定一个a -> 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<Sig>
意味着这 只有 如果你传入 特别地 一个 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);
关于这个确切的问题,我有一个完整的 post,Declarations Using Concepts。