C++概念占位符类型推导
C++ concepts placeholder type deduction
在范围规范 N4622 中,Same
概念被定义为采用两种类型 T
和 U
,但有时在 requires
中使用就像一个:
{ t } -> Same<T>;
第二类U
的推导规则是什么? (例如来自概念规范 N4630)
最简单的类似示例是:
template <class T, class U>
concept bool C = (sizeof(T) == sizeof(U)) && (sizeof(U) != 1);
template <class T>
concept bool D = requires(T t){
// How is U deduced here?
{t} -> C<T>;
};
template <class T>
requires D<T>
void fn() {}
int main() {
// Fails with: unable to deduce placeholder type 'C<char>' from 't'
// That's expected.
//fn<char>();
// But how does this work?
fn<int>();
}
使用 g++ 8.0.0 和 -fconcepts 测试样本。
什么是占位符?
快速回顾一下以确保我们都在同一页上:占位符类型几乎是一种类型,但不完全是一种类型。它是将被有效推导的类型的替代品。 (类似地,还有占位符非类型 [resp.templates],它们是非类型的替代品。为了避免切线,我将在这里提及它们的存在并使用包罗万象的 占位符 从现在起任期。)
在概念之前,我们唯一的占位符是 auto
和 decltype(auto)
说明符:
// the specifiers are all placeholders for `int`
auto i = 42;
auto& lref = i;
auto&& rref = 0;
decltype(auto) = 2 * i;
有了概念,我们可以更复杂地使用占位符:
// two placeholders for `int`
std::pair<auto, auto> p = std::make_pair(1, 4);
// one placeholder
std::pair<auto, int> q = p;
这就是它变得棘手的地方:一个概念本身可以用作占位符:
template<typename Int>
concept bool Integral = …;
// placeholder for `int` constrained by `Integral<int>`
Integral a = 0;
// same as above
Integral<> b = a;
template<typename Lhs, typename Rhs>
concept bool EqualityComparable = …;
// another placeholder for `int`
// this time constrained by `EqualityComparable<int, double>`
EqualityComparable<double> c = a;
仔细阅读二进制 EqualityComparable
示例。作为占位符的概念之所以棘手,是因为第一个概念参数有特殊处理,参数列表中将 not 提及。尖括号列表中出现的任何参数(如果有)对应于后续参数。
要求中的占位符
让我们为具有 size()
的东西写一个概念。为了演示起见,我们期望这个 size()
操作的结果应该可以用作 Incrementable
变量(而不是像 Integral
概念这样的合理的东西)。
template<typename Incr>
concept bool Incrementable = requires(Incr incr) {
++incr;
};
template<typename Cont>
concept bool Sizeable = requires(Cont const cont) {
// requirement #1
{ cont.size() } -> Incrementable
};
我们的要求 #1 是一种特殊的 复合要求。也就是说,它是一个占位符出现在句法 trailing-return-type 中的地方。效果就像我们写的一样:
template<Incrementable Incr>
void valid_for_incrementable(Incr incr);
template<typename Cont>
concept bool Sizeable = requires(Cont const cont) {
cont.size();
valid_for_incrementable(cont.size());
};
说白了,复合需求的目的是同时引入两个约束:括号中的表达式是有效的,并且它可以用作发明的约束验证函数模板的参数。
现在一起
根据我们对占位符及其在复合需求中的用途的了解,我们可以找到答案:
template<typename T>
concept bool Demo = requires(T t) {
{ t } -> C<T>;
};
实际上意味着我们在 t
表达式上引入了 C<T, T>
约束。如果占位符是 C<int>
,那么约束就会是 C<T, int>
,依此类推。
在范围规范 N4622 中,Same
概念被定义为采用两种类型 T
和 U
,但有时在 requires
中使用就像一个:
{ t } -> Same<T>;
第二类U
的推导规则是什么? (例如来自概念规范 N4630)
最简单的类似示例是:
template <class T, class U>
concept bool C = (sizeof(T) == sizeof(U)) && (sizeof(U) != 1);
template <class T>
concept bool D = requires(T t){
// How is U deduced here?
{t} -> C<T>;
};
template <class T>
requires D<T>
void fn() {}
int main() {
// Fails with: unable to deduce placeholder type 'C<char>' from 't'
// That's expected.
//fn<char>();
// But how does this work?
fn<int>();
}
使用 g++ 8.0.0 和 -fconcepts 测试样本。
什么是占位符?
快速回顾一下以确保我们都在同一页上:占位符类型几乎是一种类型,但不完全是一种类型。它是将被有效推导的类型的替代品。 (类似地,还有占位符非类型 [resp.templates],它们是非类型的替代品。为了避免切线,我将在这里提及它们的存在并使用包罗万象的 占位符 从现在起任期。)
在概念之前,我们唯一的占位符是 auto
和 decltype(auto)
说明符:
// the specifiers are all placeholders for `int`
auto i = 42;
auto& lref = i;
auto&& rref = 0;
decltype(auto) = 2 * i;
有了概念,我们可以更复杂地使用占位符:
// two placeholders for `int`
std::pair<auto, auto> p = std::make_pair(1, 4);
// one placeholder
std::pair<auto, int> q = p;
这就是它变得棘手的地方:一个概念本身可以用作占位符:
template<typename Int>
concept bool Integral = …;
// placeholder for `int` constrained by `Integral<int>`
Integral a = 0;
// same as above
Integral<> b = a;
template<typename Lhs, typename Rhs>
concept bool EqualityComparable = …;
// another placeholder for `int`
// this time constrained by `EqualityComparable<int, double>`
EqualityComparable<double> c = a;
仔细阅读二进制 EqualityComparable
示例。作为占位符的概念之所以棘手,是因为第一个概念参数有特殊处理,参数列表中将 not 提及。尖括号列表中出现的任何参数(如果有)对应于后续参数。
要求中的占位符
让我们为具有 size()
的东西写一个概念。为了演示起见,我们期望这个 size()
操作的结果应该可以用作 Incrementable
变量(而不是像 Integral
概念这样的合理的东西)。
template<typename Incr>
concept bool Incrementable = requires(Incr incr) {
++incr;
};
template<typename Cont>
concept bool Sizeable = requires(Cont const cont) {
// requirement #1
{ cont.size() } -> Incrementable
};
我们的要求 #1 是一种特殊的 复合要求。也就是说,它是一个占位符出现在句法 trailing-return-type 中的地方。效果就像我们写的一样:
template<Incrementable Incr>
void valid_for_incrementable(Incr incr);
template<typename Cont>
concept bool Sizeable = requires(Cont const cont) {
cont.size();
valid_for_incrementable(cont.size());
};
说白了,复合需求的目的是同时引入两个约束:括号中的表达式是有效的,并且它可以用作发明的约束验证函数模板的参数。
现在一起
根据我们对占位符及其在复合需求中的用途的了解,我们可以找到答案:
template<typename T>
concept bool Demo = requires(T t) {
{ t } -> C<T>;
};
实际上意味着我们在 t
表达式上引入了 C<T, T>
约束。如果占位符是 C<int>
,那么约束就会是 C<T, int>
,依此类推。