概念lite如何与通用引用相互作用?

时间:2015-03-21 11:39:44

标签: c++ c++-concepts universal-reference c++17 forwarding-reference

我最近在this video看了解C ++中概念精简的想法,这些概念很可能在今年作为TS出现。现在,我还了解了通用引用/转发引用(如here所述)和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&amp;&amp;表示通用引用而不是右值引用。

提前感谢您澄清此事!

3 个答案:

答案 0 :(得分:4)

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

另一方面,文件N4263 Toward a concept-enabled standard library是标准委员会的一些成员的意图声明,其表明在Concepts-Lite之后的自然步骤是将标准库中的概念添加到标准库中的单独TS,以便约束例如算法

TS可能有点遥远,但我们仍然可以看看到目前为止如何编写概念。我见过的大多数例子都遵循一个悠久的传统,即一切都围绕着一个假定的候选类型,通常不会被认为是一个参考类型。例如,一些较旧的Concepts-Lite草案(例如N3580)提到了诸如Container之类的概念,它们的根源在SGI STL,并且在标准库中以23.2容器要求的形式存在。

告知预转发参考符号是相关类型的描述如下:

  

值类型X::value_type存储在容器中的对象的类型。值类型必须是可分配的,但不一定是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在他的概念库中留下insightful comment,展示了确切的情况。他首选的解决方案是让概念定义适用于非引用类型,并删除约束中的引用。对于我们的例子:

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

答案 1 :(得分: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的工作原理有时会有所帮助。如果T const x;const,则无论T是否为const。如果T_constconst,则T_const x;也为constT_const const x;也是常量。 const的{​​{1}} ness是x类型的const和任何“本地”修饰符的最大值。

类似地,引用的左值是T和任何“局部”修饰符的左值的最大值。想象一下,如果该语言有两个关键字Tref。将lvalue替换为&,将lvalue ref替换为&&。在此翻译下使用ref没有lvalue是非法的。

ref表示T&&。如果T refT,则引用折叠结果为int lvalue ref - &gt; int lvalue ref ref,翻译为int lvalue ref。同样,int&会转换为T& - &gt; int lvalue ref lvalue ref,如果int lvalue ref = T,则int&&会转换为T& - &gt; int ref lvalue ref - &gt; int lvalue ref

答案 2 :(得分:3)

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

通过转发,模板参数推导经常为您提供表格(引用和cv限定类型),因此您最终会询问有关错误类型的问题。 叹息。怎么办?

您要么尝试定义概念以适应这些表单,要么尝试使用核心类型。