【问题标题】:Something like `declval` for concepts类似于 `declval` 的概念
【发布时间】:2018-12-02 01:00:52
【问题描述】:

当您使用模板和decltype 时,您经常需要某种类型的实例,即使您没有任何时间。在这种情况下,std::declval<T>() 非常有用。这会创建一个T 类型的虚构实例。

在概念上有类似的东西吗?即为概念创建和想象类型的函数。

让我举个例子(有点做作,但应该达到目的):

让我们定义一个概念Incrementable

template <typename T>
concept Incrementable = requires(T t){
   { ++t } -> T;                       
};

现在我想有一个概念来测试一个对象是否具有可以接受Incrementable 的运算符operator()。在我想象的语法中,我会这样写:

template <typename F, typename T = declval<Incrementable>>
concept OperatesOnIncrementable = requires(F f, T t){
   { f(t) } -> T;
} 

typename T = declval&lt;Incrementable&gt; 中的 declval 将创建一个虚构的类型 T,它并不是真正的具体类型,但从所有意图和目的来看,它的行为都类似于满足 Incrementable 的类型。

即将发布的标准中是否有机制允许这样做?我会发现这非常有用。


编辑:前段时间我问过similar question 是否可以用boost::hana 完成。


编辑:为什么这有用?例如,如果你想编写一个由两个函数组成的函数

template <typename F, typename G>
auto compose(F f, G g) {
  return [f, g](Incrementable auto x) { return f(g(x)); };
}

当我尝试组合两个无法组合的函数时,我想得到一个错误。在不限制 FG 类型的情况下,只有在尝试调用组合函数时才会出错。

【问题讨论】:

  • 我会发现这非常有用。”我不确定我是否明白为什么。就像,我可以理解你为什么要限制一个函数/类f(t) 是否产生T 类型的值。我理解你为什么要将T 限制为Incrementable。但我看不出在just F 的基础上约束模板有什么用处。也就是说,有人会使用OperatesOnIncrementable&lt;F&gt; 作为约束,而不提供T。那有什么好处?
  • @NicolBolas 我已经编辑了我的答案,以举一个有用的例子。
  • 为什么你的compose函数需要约束传递给内部函数的类型?如果您正在编写函数,请编写它们;让g 的性质决定作品的运作方式。
  • @NicolBolas 概念的一大优势是它们可以及早发现错误并为您提供有意义的错误消息。在我的示例中,如果您执行无效合成并将其传递给某个算法怎么办。你可能会从算法的深处得到一个可怕的错误。
  • 你想要parametricity,但 C++ 没有。模板和概念不符合条件。如果您想要一种具有参数化的语言,请考虑 Haskell、OCaml 或 Scala。

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


【解决方案1】:

没有这样的机制。

这似乎也不是可实现/有用的,因为Incrementable 类型的数量是无限的,并且F 可以拒绝使用任意复杂的元程序选择的子集。因此,即使您可以神奇地合成一些独特的类型,您仍然不能保证 F 对所有 Incrementable 类型都有效。

【讨论】:

  • 如果元程序使用其他东西,我们不能让元程序失败吗?那么它是由概念保证的吗?
【解决方案2】:

在概念上有类似的东西吗?即为概念创建和想象类型的函数。

这个术语是一个原型。提出原型将是一个非常有价值的功能,并且对于执行定义检查之类的事情至关重要。来自 T.C. 的回答:

因此,即使您可以神奇地合成一些独特的类型,您仍然不能保证 F 对所有 Incrementable 类型都有效。

这样做的方法是合成一个尽可能满足概念标准的原型。正如他所说,C++20 中没有原型生成,考虑到当前的概念化身,这似乎是不可能的。


想出正确的原型非常困难。例如,对于

template <typename T>
concept Incrementable = requires(T t){
   { ++t } -> T;                       
};

写起来很诱人:

struct Incrementable_archetype {
    Incrementable_archetype operator++();
};

但这并不是“尽可能小”——这种类型是默认可构造和可复制的(不是Incrementable 强加的要求),并且它的operator++ 完全返回T,这也不是要求。所以一个真正的硬核原型看起来像:

struct X {
    X() = delete;
    X(X const&) = delete;
    X& operator=(X const&) = delete;

    template <typename T> operator,(T&&) = delete;

    struct Y {
        operator X() &&;
    };
    Y operator++();
};

如果您的函数适用于X,那么它可能适用于所有Incrementable 类型。如果您的函数不适用于 X,那么您可能需要更改实现以使其正常工作,或者更改约束以允许更多功能。

有关更多信息,请查看 Boost Concept Check Library,它已经很老了,但至少阅读文档非常有趣。

【讨论】:

  • “可能”是关键字。我可以给F 一个void operator()(some-Incrementable-type) = delete; 而你的原型无法检测到。
猜你喜欢
  • 1970-01-01
  • 2011-07-21
  • 2012-07-26
  • 2015-07-15
  • 2016-03-19
  • 1970-01-01
  • 2021-11-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多