精简概念将如何与通用参考交互?
How will concepts lite interact with universal references?
我最近看了 this video explaining the ideas of concepts lite in C++, which are likely to appear this year as a TS. Now, I also learned about universal references/forwarding references (as described 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&& 表示通用引用而不是右值引用。
提前感谢您对此的澄清!
T&&
始终具有相同的 "meaning" -- 它是对 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_const
是 const
,那么 T_const x;
也是 const
。 T_const const x;
也是常量。 x
的 const
ness 是类型 T
和任何 "local" 修饰符的 const
ness 的最大值。
类似地,引用的左值是 T
和任何 "local" 修饰符的左值的最大值。想象一下,如果该语言有两个关键字,ref
和 lvalue
。将 &
替换为 lvalue ref
,将 &&
替换为 ref
。使用 lvalue
而没有 ref
在此翻译下是非法的..
T&&
表示 T ref
。如果 T
是 int 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&
.
这完全取决于概念本身是如何编写的。 Concepts-Lite 本身(latest TS 在撰写本文时)在这个问题上是不可知的:它定义了可以在语言中定义和使用概念的机制,但不向库中添加库存概念。
另一方面,文档 N4263 Toward a concept-enabled standard library 是标准委员会一些成员的意向声明,它建议在 Concepts-Lite 之后的自然步骤是一个单独的 TS,用于将概念添加到标准库中,以便约束例如算法。
那个 TS 可能有点落后,但我们仍然可以看看到目前为止是如何编写概念的。我见过的大多数例子都多少遵循了一个悠久的传统,即一切都围绕着一个假定的、通常不会被认为是引用类型的候选类型。例如,一些较旧的 Concepts-Lite 草稿(例如 N3580) mention concepts such as Container which have their roots in the SGI STL 甚至今天以 23.2 容器要求的形式在标准库中仍然存在。
一个明显的预转发参考标志是关联类型的描述如下:
Value type X::value_type
The type of the object stored in a container. The value type must be Assignable, but need not be 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);
这是一件很难的事情。大多数情况下,当我们写概念时,我们希望专注于类型定义(我们可以用 T
做什么)而不是它的各种形式(const T
、T&
、T const&
, ETC)。你一般问的是,"can I declare a variable like this? Can I add these things?"。无论参考资料或简历资格如何,这些问题往往都是有效的。除非他们不是。
通过转发,模板参数推导通常会为您提供超出形式的参数(引用和 cv 限定类型),因此您最终会询问有关错误类型的问题。 感叹。怎么办?
您要么尝试定义概念以适应这些形式,要么尝试获得核心类型。
我最近看了 this video explaining the ideas of concepts lite in C++, which are likely to appear this year as a TS. Now, I also learned about universal references/forwarding references (as described 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&& 表示通用引用而不是右值引用。
提前感谢您对此的澄清!
T&&
始终具有相同的 "meaning" -- 它是对 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_const
是 const
,那么 T_const x;
也是 const
。 T_const const x;
也是常量。 x
的 const
ness 是类型 T
和任何 "local" 修饰符的 const
ness 的最大值。
类似地,引用的左值是 T
和任何 "local" 修饰符的左值的最大值。想象一下,如果该语言有两个关键字,ref
和 lvalue
。将 &
替换为 lvalue ref
,将 &&
替换为 ref
。使用 lvalue
而没有 ref
在此翻译下是非法的..
T&&
表示 T ref
。如果 T
是 int 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&
.
这完全取决于概念本身是如何编写的。 Concepts-Lite 本身(latest TS 在撰写本文时)在这个问题上是不可知的:它定义了可以在语言中定义和使用概念的机制,但不向库中添加库存概念。
另一方面,文档 N4263 Toward a concept-enabled standard library 是标准委员会一些成员的意向声明,它建议在 Concepts-Lite 之后的自然步骤是一个单独的 TS,用于将概念添加到标准库中,以便约束例如算法。
那个 TS 可能有点落后,但我们仍然可以看看到目前为止是如何编写概念的。我见过的大多数例子都多少遵循了一个悠久的传统,即一切都围绕着一个假定的、通常不会被认为是引用类型的候选类型。例如,一些较旧的 Concepts-Lite 草稿(例如 N3580) mention concepts such as Container which have their roots in the SGI STL 甚至今天以 23.2 容器要求的形式在标准库中仍然存在。
一个明显的预转发参考标志是关联类型的描述如下:
Value type
X::value_type
The type of the object stored in a container. The value type must be Assignable, but need not be 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);
这是一件很难的事情。大多数情况下,当我们写概念时,我们希望专注于类型定义(我们可以用 T
做什么)而不是它的各种形式(const T
、T&
、T const&
, ETC)。你一般问的是,"can I declare a variable like this? Can I add these things?"。无论参考资料或简历资格如何,这些问题往往都是有效的。除非他们不是。
通过转发,模板参数推导通常会为您提供超出形式的参数(引用和 cv 限定类型),因此您最终会询问有关错误类型的问题。 感叹。怎么办?
您要么尝试定义概念以适应这些形式,要么尝试获得核心类型。