我最近在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;表示通用引用而不是右值引用。
提前感谢您澄清此事!
答案 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_const
为const
,则T_const x;
也为const
。 T_const const x;
也是常量。 const
的{{1}} ness是x
类型的const
和任何“本地”修饰符的最大值。
类似地,引用的左值是T
和任何“局部”修饰符的左值的最大值。想象一下,如果该语言有两个关键字T
和ref
。将lvalue
替换为&
,将lvalue ref
替换为&&
。在此翻译下使用ref
没有lvalue
是非法的。
ref
表示T&&
。如果T ref
为T
,则引用折叠结果为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 T
,T&
,T const&
等)。你通常会问的是,“我可以声明一个这样的变量吗?我可以添加这些东西吗?”。无论参考资料或资格证书如何,这些问题都有效。除非他们不是。
通过转发,模板参数推导经常为您提供表格(引用和cv限定类型),因此您最终会询问有关错误类型的问题。 叹息。怎么办?
您要么尝试定义概念以适应这些表单,要么尝试使用核心类型。