11

我最近看了这个视频,解释了 C++ 中精简概念的想法,今年可能会以 TS 的形式出现。现在,我还了解了通用引用/转发引用(如此所述),并且 T&& 可以根据上下文有两种含义(即是否正在执行类型推导)。这自然会引出一个问题,概念将如何与通用引用相互作用?

具体来说,在下面的例子中,我们有

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&& 表示通用引用而不是右值引用。

提前感谢您对此的澄清!

4

3 回答 3

5

这完全取决于概念本身的编写方式。Concepts-Lite 本身(撰写本文时的最新 TS)对此事是不可知的:它定义了可以在语言中定义和使用概念的机制,但不会将库存概念添加到库中。

另一方面,文档N4263 Toward a concept-enabled standard library是标准委员会的一些成员的意向声明,它建议在 Concepts-Lite 之后的自然步骤是一个单独的 TS,用于将概念添加到标准库中以限制例如算法。

那个 TS 可能有点远,但我们仍然可以看看到目前为止的概念是如何编写的。我见过的大多数示例都遵循了一个悠久的传统,即一切都围绕着一个假定的候选类型,通常不期望它是一个引用类型。例如,一些较旧的 Concepts-Lite 草案(例如N3580)提到了诸如 Container 之类的概念,它们起源于SGI STL,甚至在今天仍以 23.2 Container 要求的形式存在于标准库中。

一个明显的预转发参考符号是关联类型的描述如下:

值类型 X::value_type 存储在容器中的对象的类型。值类型必须是 Assignable,但不必是 DefaultConstructible。

如果我们将它天真地翻译成 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

我相信委员会成员都知道情况。例如,Andrew Sutton 在他的概念库中留下了有见地的评论,展示了确切的情况。他首选的解决方案是让概念定义处理非引用类型,并删除约束中的引用。对于我们的示例:

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);
于 2015-03-21T13:52:13.730 回答
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&&它只会绑定到右值。

由于其余示例(最后我检查过)是根据第一个示例定义的,所以你有它。

概念规范中可能有额外的“魔法”,但我不知道。


1实际上从来没有引用到引用。事实上,如果你输入int y = 3; int& && x = y;的是一个非法的表达式:但是using U = int&; U&& x = y;是完全合法的,因为会发生引用折叠。

对工作原理的类比const有时会有所帮助。If T const x;isconst不管 if Tis const。如果T_constconst,那么T_const x;也是constT_const const x;也是 const 。constness of是类型和任何“本地”修饰符的x最大值。constT

类似地,引用的左值是 theT和任何“本地”修饰符的左值的最大值。想象一下,如果该语言有两个关键字,ref并且lvalue. 替换&lvalue ref和。&&_ ref在此翻译下使用lvaluewithoutref是非法的。

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&

于 2015-03-21T13:32:20.530 回答
3

这是一件困难的事情。大多数情况下,当我们编写概念时,我们希望关注类型定义(我们可以用 做什么T)而不是它的各种形式(const TT&T const&等)。您通常会问的是,“我可以声明这样的变量吗?我可以添加这些东西吗?”。无论参考资料或简历资格如何,这些问题往往都是有效的。除非他们不是。

使用转发,模板参数推导经常给你那些形式(引用和 cv 限定类型),所以你最终会问关于错误类型的问题。叹息。该怎么办?

您要么尝试定义概念以适应这些形式,要么尝试获得核心类型。

于 2015-09-08T02:48:54.223 回答