为什么在概念中使用std :: forward? [英] Why use std::forward in concepts?

查看:102
本文介绍了为什么在概念中使用std :: forward?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在阅读约束上的cppreference页面,并注意到以下示例:

I was reading the cppreference page on Constraints and noticed this example:

// 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 。是否尝试在模板参数中支持引用类型?我们是否不想用lvalues调用 swap ,并且当<$ c时 forward 表达式不是rvalues $ c> T 和 U 是标量(非引用)类型?

I'm puzzled why they're using std::forward. Some attempt to support reference types in the template parameters? Don't we want to call swap with lvalues, and wouldn't the forward expressions be rvalues when T and U are scalar (non-reference) types?

例如,鉴于他们的 Swappable 实现,我希望该程序失败:

For example, I would expect this program to fail given their Swappable implementation:

#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给了我一个内部编译器错误,对此没有多大说明。

Unfortunately g++ 7.1.0 gives me an internal compiler error, which doesn't shed much light on this.

此处 T U 都应为 MyType ,和 std :: forward< T(t)应该返回 MyType& ,该值不能传递到我的 swap 函数。

Here both T and U should be MyType, and std::forward<T>(t) should return MyType&&, which can't be passed to my swap function.

此实现是 Swappable 错了吗?我错过了什么吗?

Is this implementation of Swappable wrong? Have I missed something?

推荐答案


我们是否不希望使用左值调用swap […]

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

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

That’s a very good question. A question of API design specifically: what meaning or meanings should the designer of a concept library give to the parameters of its concepts?

可交换需求的快速回顾。也就是说,实际的要求已经出现在今天的标准中,并且在自lites-lite之前就已经存在:

A quick recap on Swappable requirements. That is, the actual requirements that already appear in today’s Standard and have been here since before concepts-lite:



  • 对象 t 可以与对象 u 交换,且仅当:


    • […]表达式 swap(t,u) swap(u ,t)有效[…]

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

[…]

右值或左值 t 是可交换的,并且仅当t可与任何右值或左值交换时分别为 T 类型。

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] 来减少大量不相关的细节。)

(Excerpts butchered from Swappable requirements [swappable.requirements] to cut down on a whole lot of irrelevant details.)

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

Did you catch that? The first bit gives requirements that match your expectations. It’s quite straightforward to turn into an actual concept†, too:

†:只要我们愿意忽略我们之外的大量细节范围

template<typename Lhs, typename Rhs = Lhs>
concept bool FirstKindOfSwappable = requires(Lhs lhs, Rhs rhs) {
    swap(lhs, rhs);
    swap(rhs, lhs);
};

现在,非常重要的是,我们应该立即注意到该概念立即支持引用变量:

Now, very importantly we should immediately notice that this concept supports reference variables right out of the box:

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&&> );

(现在,从标准上讲,标准是在引用引用不是的对象方面进行讨论。引用不仅指对象或函数,而且旨在透明地代表它们,我们实际上提供了一个非常理想的功能,实际上,我们现在是在变量,而不仅仅是对象。)

(Now technically the Standard was talking in terms of objects which references aren't. Since references not only refer to objects or functions but are meant to transparently stand for them, we’ve actually provided a very desirable feature. Practically speaking we are now working in terms of variables, not just objects.)

这里有一个非常重要的联系: int&& c是变量的声明类型,以及传递给该概念的实际参数,依次又以 lhs 和<$ c $的声明类型结束c> rhs 需要参数。

There’s a very important connection here: int&& is the declared type of our variables, as well as the actual argument passed to the concept, which in turn ends up again as the declared type of our lhs and rhs requires parameters. Keep that in mind as we dig deeper.

Coliru 演示

现在该怎么办第二位提到左值和右值?嗯,这里我们不再处理变量,而是根据 expressions 进行处理。我们可以为此写一个概念吗?好吧,我们可以使用某种表达式到类型的编码。即 decltype 以及 std :: declval 在另一个方向上使用的那个。这导致我们:

Now what about that second bit that mentions lvalues and rvalues? Well, here we’re not dealing in variables any more but instead in terms of expressions. Can we write a concept for that? Well, there’s a certain expression-to-type encoding we can use. Namely the one used by decltype as well as std::declval in the other direction. This leads us to:

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

您遇到的是什么!正如您所发现的,必须以不同的方式使用该概念:

Which is what you ran into! And as you found out, the concept must be used in a different way:

// 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&>()在我们约束中的表达式。或以更round回的方式,通过 std :: forward< int&(lhs)

If you find that non-obvious, take a look at the connection at play this time: we have an lvalue expression of type int, which becomes encoded as the int& argument to the concept, which gets restored to an expression in our constraint by std::declval<int&>(). Or in a more roundabout way, by std::forward<int&>(lhs).

< a href = http://coliru.stacked-crooked.com/a/40ca85ebafc41652 rel = nofollow noreferrer> Coliru 演示

cppreference条目上显示的是指定的 Swappable 概念的摘要由Ranges TS提供。如果我猜到了,我会说Ranges TS决定提供 Swappable 参数来表示表达式,原因如下:

What appears on the cppreference entry is a summary of the Swappable concept specified by the Ranges TS. If I were to guess, I would say that the Ranges TS settled on giving the Swappable parameters to stand for expressions for the following reasons:


  • 我们可以用 FirstKindOfSwappable 来写 SecondKindOfSwappable 由以下几乎给出:

template<typename Lhs, typename Rhs = Lhs>
concept bool FirstKindOfSwappable = SecondKindOfSwappable<Lhs&, Rhs&>;

此方法可以在很多情况下应用,但并非在所有情况下都可以使用,因此有时可以表达参数化的概念就变量类型而言,隐含在类型表达式中的参数相同。

This recipe can be applied in many but not all cases, making it sometimes possible to express a concept parametrised on types-of-variables in terms of the same concept parametrised on expressions-hidden-in-types. But it’s usually not possible to go the other way around.

约束 swap(std :: forward< Lhs>(lhs)),但通常是不可能的。 ,std :: forward< Rhs>(rhs))被认为是足够重要的情况;从我头顶掉出来的事情就出现了,例如:

constraining on swap(std::forward<Lhs>(lhs), std::forward<Rhs>(rhs)) is expected to be an important enough scenario; off the top of my head it comes up in business such as:

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的其他概念

  • consistency: for the most part, other concepts of the TS follow the same convention and are parametrised over types of expressions

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

    But why for the most part?


    $ b

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

    Because there is a third kind of concept parameter: the type that stand for… a type. A good example of that is DerivedFrom<Derived, Base>() which value does not give you valid expressions (or ways to use variables) in the usual sense.

    实际上,例如 Constructible< Arg,Inits ...>()第一个参数 Arg 可以用两种方式解释:

    In fact, in e.g. Constructible<Arg, Inits...>() the first argument Arg can arguably be interpreted in two ways:


    • Arg 代表一种类型,即将可构造性视为a的固有属性。 type

    • Arg 是正在构造的变量的声明类型,即约束表示 Arg imaginary_var { std :: declval< Inits>()...}; 有效

    • Arg stands for a type, i.e. taking constructibility as an inherent property of a type
    • Arg is the declared type of a variable being constructed, i.e. the constraint implies that Arg imaginary_var { std::declval<Inits>()... }; is valid

    我会总结一下自己的看法:我认为读者不应得出结论,他们应该以同样的方式编写自己的概念,因为至少从概念编写者的角度来看,概念超过表达式似乎是变量之上的概念的超集。

    I’ll conclude with a personal note: I think the reader should not conclude (yet) that they should write their own concepts the same way just because concepts over expressions appear, at least from the perspective of a concept writer, to be a superset of concepts over variables.

    还有其他因素在起作用,我担心的是即从概念客户端的角度来看具有可用性,而我仅在顺便提及了所有这些细节,也。但这确实与问题无关,这个答案已经足够长了,所以我再讲一次这个故事。

    There are other factors at play, and my concern is namely with usability from the perspective of a concept client and all these details I only mentioned in passing, too. But that doesn’t really have to do with the question and this answer is already long enough, so I’ll leave that story for another time.

    这篇关于为什么在概念中使用std :: forward?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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