概念轻量级将如何与通用引用交互?

14

我最近看过这个视频,它解释了C++中的Concepts Lite的思想,这些思想很可能会在今年作为TS出现。现在,我也了解到关于万能引用/转发引用的知识(如这里所描述),以及T&&在不同上下文中有两种含义的情况(即是否进行类型推导)。这自然地引出了一个问题,那就是Concepts和万能引用之间将如何交互?

为了具体说明,我们可以看下面的例子:

void f(int&& i) {}

int i = 0;
f(i);    // error, looks for f(int&)
f(0);    // fine, calls f(int&&)

并且

template <typename T>
void f(T&& test) {}

int i = 0;
f(i);    // fine, calls f(T&&) with T = int& (int& && = int&)
f(0);    // fine, calls f(T&&) with T = int&& (int&& && = int&&)

但是如果我们使用概念会发生什么?

template <typename T>
    requires Number<T>
void f(T&& test) {}

template <Number T>
void g(T&& test) {}

void h(Number&& test) {}

int i = 0;
f(i);    // probably should be fine?
f(0);    // should be fine anyway
g(i);    // probably also fine?
g(0);    // fine anyway
h(i);    // fine or not?
h(0);    // fine anyway

特别是最后一个例子让我有些困扰,因为存在两个相互冲突的原则。首先,以这种方式使用的概念应该像类型一样工作;其次,如果T是一个推导类型,则T&&表示通用引用而不是右值引用。

感谢您提前对此进行澄清!


在您的第二个示例中,对于f(0),T将是int,而不是int&& - palapapa
在你的第二个例子中,对于 f(0),T 应该是 int,而不是 int&& - undefined
3个回答

6

一切取决于概念本身的编写方式。就概念本身而言(截至本文最新TS),Concepts-Lite在这个问题上是中立的:它定义了概念可以如何在语言中定义和使用的机制,但不会向库中添加常规概念。

另一方面,文件N4263 Toward a concept-enabled standard library是标准委员会某些成员的声明,建议在Concepts-Lite之后的自然步骤是添加概念到标准库中,以限制例如算法等。

那么TS可能还有一段路要走,但我们仍然可以看一下目前为止概念是如何编写的。我看到的大多数示例都遵循一个长期传统,即所有内容都围绕着一个假定的、候选类型展开,通常不希望它是引用类型。例如,一些早期的Concepts-Lite草案(例如N3580)提到了诸如Container这样的概念,它们起源于SGI STL,甚至在标准库中以23.2 Container需求的形式存活至今。
一个显而易见的前向引用信号是描述关联类型的方式:
值类型X::value_type 存储在容器中的对象的类型。值类型必须是可分配的,但不需要是默认构造的。
如果我们单纯地将其翻译为Concepts-Lite,则可能如下所示:
template<typename X>
concept bool Container = requires(X x) {
   typename X::value_type;
   // other requirements...
};

如果我们写

template<typename C>
    requires Container<C>
void example(C&& c);

然后我们有以下行为:
std::vector<int> v;

// fine
// this checks Container<std::vector<int>>, and finds
// std::vector<int>::value_type
example(std::move(v));

// not fine
// this checks Container<std::vector<int>&>, and
// references don't have member types
example(v);

有几种方法可以表达value_type要求,以优雅地处理这种情况。例如,我们可以将要求调整为typename std::remove_reference_t<X> :: value_type

我相信委员会成员已经意识到了这种情况。例如,安德鲁·萨顿在他的概念库中留下了一个深刻的评论,展示了确切的情况。他首选的解决方案是将概念定义留给非引用类型,并在约束条件中删除引用。对于我们的例子:

template<typename C>
    // Sutton prefers std::common_type_t<C>,
    // effectively the same as std::decay_t<C>
    requires<Container<std::remove_reference_t<C>>>
void example(C&& c);

"(截至本文撰写时的最新TS版本)" 这个N4377是什么意思? - dyp
1
@dyp 我认为这是 ISO 的其中一种情况,两份文件涵盖了相同的变更。一份是 TS,另一份则是拟议中的草案,具体是什么我不太清楚。 - Luc Danton
这意味着,如果您想要防止意外的转发行为,那么您必须确保每个自定义概念都不是引用,对吗?如果一个概念默认情况下可以确保这一点,那么用户始终需要显式地删除引用才能使用转发语法,这样不是更简单吗? - BeABee
@BeABee 有时引用类型是一个完全合适的概念模型。但有时引用类型不匹配,你真正希望要求它们被拒绝。你的建议并不能处理这两种情况。 - Luc Danton

3

T&& 总是具有相同的“含义”--它是对 T 的右值引用。

T 本身是一个引用时,有趣的事情发生了。如果 T=X&&,那么 T&& = X&&。如果 T=X&,那么 T&& = X&。将左值引用的右值引用作为左值引用的规则是允许存在转发引用技术的。这就是所谓的引用折叠1

因此,关于

template <typename T>
  requires Number<T>
void f(T&& test) {}

这取决于Number<T>的含义。如果Number<T>允许传递左值引用,那么T&&将像转发引用一样工作。否则,T&&只会绑定到右值。
由于其余示例通常是在第一个示例中定义的(最后我检查了一下),因此您可以在这里找到它们。
可能还有其他"magic"在概念规范中,但我不知道。 1实际上永远不存在对引用的引用。事实上,如果您键入int y = 3; int& && x = y;,那是非法表达式:但是using U = int&; U&& x = y;是完全合法的,因为引用折叠发生。
类比一下const的工作方式有时会有所帮助。如果T const x;const的,而不管T是否为const。如果T_constconst,那么T_const x;也是const的。而T_const const x;也是const的。引用的lvalue-ness与T的lvalue-ness和任何"local"修饰符的最大值相同。想象一下如果语言中有两个关键字,reflvalue。将&替换为lvalue ref&&替换为ref。在此转换下,使用没有reflvalue是非法的。 T&&表示T ref。如果Tint lvalue ref,则引用折叠结果为int lvalue ref ref->int lvalue ref,然后再次转换为int&。同样,T&转换为int lvalue ref lvalue ref->int lvalue ref,如果T= int&&,则T&转换为int ref lvalue ref->int lvalue ref->int&

3
2015年2月9日的N4377仍然像g一样定义了hg又像f一样:从缩写函数模板开始,引入一个带有约束参数 [dcl.fct] p16-18的模板参数列表,然后从概念的原型参数中引入一个类型模板参数 [temp.param] p9,最后从概念中引入一个谓词约束 [temp.param] p10。 - dyp
不存在将左值引用作为右值引用的rvalue引用,实际上根据§8.3.2/5,“不得存在对引用的引用[...]”。虽然存在引用折叠,但我认为有更好的方法来解释它。 - Luc Danton
@LucDanton 对于不使用标准语言的引用折叠进行了长时间的讨论。你看到有任何错误吗? - Yakk - Adam Nevraumont

3
这是一件棘手的事情。通常,当我们编写概念时,我们想关注类型定义(我们可以用T做什么),而不是它的各种形式(const TT&T const&等)。你通常会问的是,“我能像这样声明变量吗?我能添加这些内容吗?”这些问题往往是有效的,无论是否涉及引用或cv限定符。除非有例外。
通过转发,模板参数推导通常会给你那些形式(引用和带cv限定的类型),所以你最终会对错误的类型提出问题。叹气。该怎么办?
要么尝试定义概念来适应这些形式,要么尝试获取核心类型。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接