gpt4 book ai didi

c++ - 为什么在概念中使用std::forward?

转载 作者:IT老高 更新时间:2023-10-28 21:43:05 31 4
gpt4 key购买 nike

我正在阅读cppreference page on Constraints并注意到以下示例:

// example constraint from the standard library (ranges TS)
template <class T, class U = T>
concept bool Swappable = requires(T t, U u) {
swap(std::forward<T>(t), std::forward<U>(u));
swap(std::forward<U>(u), std::forward<T>(t));
};

我不知道他们为什么要使用 std::forward。是否尝试在模板参数中支持引用类型?我们是否不想用左值调用 swap,并且当 forwardT是标量(非引用)类型时, U表达式不是右值吗?

例如,鉴于其 Swappable实现,我希望该程序失败:
#include <utility>

// example constraint from the standard library (ranges TS)
template <class T, class U = T>
concept bool Swappable = requires(T t, U u) {
swap(std::forward<T>(t), std::forward<U>(u));
swap(std::forward<U>(u), std::forward<T>(t));
};

class MyType {};
void swap(MyType&, MyType&) {}

void f(Swappable& x) {}

int main()
{
MyType x;
f(x);
}

不幸的是,g++ 7.1.0给了我一个 internal compiler error,但对此并没有多大帮助。

此处 TU都应为 MyType,而 std::forward<T>(t)应返回 MyType&&,但无法将其传递给我的 swap函数。
Swappable的此实现是否错误?我错过了什么吗?

最佳答案

Don't we want to call swap with lvalues […]



这是一个很好的问题。 API设计的一个具体问题:概念库的设计者应赋予其概念参数什么意思?

快速回顾可交换需求。就是说,实际的要求已经出现在今天的标准中,并且早于concept-lite以来就存在于这里:

  • An object t is swappable with an object u if and only if:
    • […] the expressions swap(t, u) and swap(u, t) are valid […]

[…]

An rvalue or lvalue t is swappable if and only if t is swappable with any rvalue or lvalue, respectively, of type T.



(摘录自 Swappable requirements [swappable.requirements],以减少许多不相关的细节。)

变数

你明白了吗?第一部分提供符合您期望的要求。变成一个实际的概念也很简单†:

†:只要我们愿意忽略我们范围之外的大量细节
template<typename Lhs, typename Rhs = Lhs>
concept bool FirstKindOfSwappable = requires(Lhs lhs, Rhs rhs) {
swap(lhs, rhs);
swap(rhs, lhs);
};

现在,非常重要的是,我们应该立即注意到该概念开箱即用地支持引用变量:
int&& a_rref = 0;
int&& b_rref = 0;
// valid...
using std::swap;
swap(a_rref, b_rref);
// ...which is reflected here
static_assert( FirstKindOfSwappable<int&&> );

(现在,从技术上讲,该标准是在谈论对象,而引用不是。)由于引用不仅指对象或函数,而且旨在透明地代表它们,因此,我们实际上提供了一个非常理想的功能。实际上,我们现在已经有了就变量而言,而不仅仅是对象。)

这里有一个非常重要的联系: int&&是我们的变量的声明类型,以及传递给概念的实际参数,继而又以我们的 lhsrhs的声明类型需要参数而结束。当我们深入研究时,请记住这一点。

Coliru demo

表达方式

现在第二个提到左值和右值的位呢?好吧,这里我们不再处理变量,而是根据表达式进行处理。我们可以为此写一个概念吗?好吧,我们可以使用某种表达式到类型的编码。即 decltype以及另一个方向上的 std::declval使用的那个。这导致我们:
template<typenaome Lhs, typename Rhs = Lhs>
concept bool SecondKindOfSwappable = requires(Lhs lhs, Rhs rhs) {
swap(std::forward<Lhs>(lhs), std::forward<Rhs>(rhs));
swap(std::forward<Rhs>(rhs), std::forward<Lhs>(lhs));

// another way to express the first requirement
swap(std::declval<Lhs>(), std::declval<Rhs>());
};

您遇到了什么!正如您所发现的,必须以不同的方式使用该概念:
// not valid
//swap(0, 0);
// ^- rvalue expression of type int
// decltype( (0) ) => int&&
static_assert( !SecondKindOfSwappable<int&&> );
// same effect because the expression-decltype/std::declval encoding
// cannot properly tell apart prvalues and xvalues
static_assert( !SecondKindOfSwappable<int> );

int a = 0, b = 0;
swap(a, b);
// ^- lvalue expression of type int
// decltype( (a) ) => int&
static_assert( SecondKindOfSwappable<int&> );

如果发现不明显,请看一下这次的连接:我们有一个类型为 int的左值表达式,该表达式被编码为该概念的 int&参数,并通过 std::declval<int&>()恢复为我们约束中的表达式。或者通过 std::forward<int&>(lhs)以更round回的方式。

Coliru demo

把它放在一起

在cppreference条目上显示的是Ranges TS指定的 Swappable概念的摘要。如果我猜到了,我会说Ranges TS决定提供 Swappable参数来表示表达式,原因如下:
  • 我们可以用SecondKindOfSwappable来编写FirstKindOfSwappable,如下所示:
    template<typename Lhs, typename Rhs = Lhs>
    concept bool FirstKindOfSwappable = SecondKindOfSwappable<Lhs&, Rhs&>;

    此配方可以在很多但不是全部情况下应用,有时使表达在变量类型上的参数化概念成为可能,这与在表达式中隐藏类型的参数化相同。但是通常不可能采取其他方法。
  • 限制 swap(std::forward<Lhs>(lhs), std::forward<Rhs>(rhs))
  • 被认为是足够重要的方案。从我的头顶上浮现出来,例如:
    template<typename Val, typename It>
    void client_code(Val val, It it)
    requires Swappable<Val&, decltype(*it)>
    // ^^^^^^^^^^^^^--.
    // |
    // hiding an expression into a type! ------`
    {
    ranges::swap(val, *it);
    }
  • 一致性:在大多数情况下,TS的其他概念遵循相同的约定,并根据表达式的类型
  • 进行参数化

    但是为什么在大多数情况下呢?

    因为存在第三种概念参数:代表……一种类型的类型。一个很好的例子是 DerivedFrom<Derived, Base>(),它的值通常不会提供有效的表达式(或使用变量的方式)。

    实际上,例如 Constructible<Arg, Inits...>()第一个参数 Arg可以用两种方式解释:
  • Arg代表一种类型,即将可构造性作为
  • 类型的固有属性
  • Arg是正在构造的变量的声明类型,即约束表示Arg imaginary_var { std::declval<Inits>()... };是有效的

  • 我应该如何写自己的概念?

    我将总结一下个人观点:我认为读者不应得出结论,他们应该以同样的方式编写自己的概念,仅是因为至少在概念作者看来,超过表达式的概念似乎是一个超集变量之上的概念。

    还有其他因素在起作用,我的关注点就是从概念客户的角度来考虑可用性,我也只顺便提到了所有这些细节。但这与问题确实无关,这个答案已经足够长了,因此我将把这个故事再讲一次。

    关于c++ - 为什么在概念中使用std::forward?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44356761/

    31 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com