如何在 C++20 中为模板别名创建推导指南?
How can I create deduction guides for template aliases in C++20?
假设我有一个 class/struct 模板及其构造函数的显式推导指南。让这个class有两个模板参数,其中一个可以通过推导指南推导,另一个不能。
template <int Q, typename T>
struct Foo {
template <typename F>
Foo(F&&) { }
};
template <typename T>
using alias = T;
template <typename T>
struct alias2 { using type = T; };
template <int Q, typename F>
Foo(F&& f) -> Foo<Q, alias<F>>; // deduction guide, but cannot deduce Q yet
template <typename T>
using Bar = Foo<1, T>; // create alias that fixes Q
/* This should generate a deduction guide for Bar<T> by first
"copying" Foo's deduction guide, deducing from Foo<Q, alias<F>>
and Foo<1, T> that Q=1 and T=alias<F>=F, thus generating
<template F>
Bar(F&&) -> Bar<1, F>;
if this was correct syntax. */
int main() {
Bar f{ 5 };
}
如果我现在创建一个别名来显式指定以前不可推导的参数,as far as I understand,这个别名的隐式生成推导指南应该能够完全推导两个模板参数(根据标准模板参数推导规则),即使在定义 class 模板中未推导一种类型。
但是不使用alias
,而是使用alias2
的场景怎么办,即将推导指南改成
template <int Q, typename F>
Foo(F&& f) -> Foo<Q, typename alias2<F>::type>;
根据 documentation, this would now introduce a non-deduced context (as the template parameter appears left to a scope operator ::
), so the template argument deduction for T=F
should fail (which apparently does).
问题一:如果这个理论是正确的,我能做些什么吗?假设我不想使用一个普通的身份别名,而是一个更复杂的类型转换,最终将在推导指南中具有 typename transformation<Input>::result
的形状。
问题 2:即使现在,当我完全删除 Q 时,我的理论仍然失败,因为以下代码将被接受(GCC-10/11):
template <typename T>
struct Foo {
template <typename F>
Foo(F&&) { }
};
template <typename T>
struct alias2 { using type = T; };
template <typename F>
Foo(F&& f) -> Foo<typename alias2<F>::type>;
template <typename T>
using Bar = Foo<T>;
template <typename T>
void some(typename alias2<T>::type) { }
int main() {
Bar f{ 5 };
}
为什么编译器能够从 F 推导出 T,即使这是一个非推导上下文?
为了做你想做的事,C++ 必须能够反转图灵完备的子程序。
图灵完备程序不仅不可逆,而且无法确定给定的图灵完备程序是否可逆。您可以定义可逆的子语言,但这些子语言缺乏图灵完备的能力。
推导 Bar
别名参数:
template <typename T>
using Bar = Foo<1, T>;
需要反转第二个模板参数 alias<F>
才能找到 F
。当 alias
是普通模板别名时,这是可能的、允许的,并且是 C++ 标准所要求的。
当alias2
评估为foo<F>::type
时,这样的构造能够进行图灵完备计算。 C++ 标准没有让编译器 try 反转这样的计算,而是统一说“不要尝试”。它通常使用“依赖类型”来阻止这种反转尝试。
在你的第二种情况下,Bar
是 Foo
的普通别名。 Foo
有推导指南。该推导指南告诉我们如何从 F
到 T
,因此编译器不必反转任何可能的图灵完备程序来确定 T
.
C++ 语言有一堆措辞允许只是重命名参数等的模板别名就像它们是原始类型一样。本来连个玩具的别名都会挡住一堆这样的推演;但后来发现这是一个糟糕的计划。所以他们在标准中添加了文本来描述什么样的模板别名在那里“微不足道”,并修改了推导规则的措辞,以便将它们视为透明。
为了在类型推导期间反转任意图灵完备程序(事实上,几乎任何结构上非平凡的类型转换),您必须明确给出反转。
一旦你接受了这一点,它就变成了与语法的斗争,而不是概念上的斗争。
我不知道别名模板的自定义模板推导指南的现状。上次听说不支持,不过最近没看。
假设我有一个 class/struct 模板及其构造函数的显式推导指南。让这个class有两个模板参数,其中一个可以通过推导指南推导,另一个不能。
template <int Q, typename T>
struct Foo {
template <typename F>
Foo(F&&) { }
};
template <typename T>
using alias = T;
template <typename T>
struct alias2 { using type = T; };
template <int Q, typename F>
Foo(F&& f) -> Foo<Q, alias<F>>; // deduction guide, but cannot deduce Q yet
template <typename T>
using Bar = Foo<1, T>; // create alias that fixes Q
/* This should generate a deduction guide for Bar<T> by first
"copying" Foo's deduction guide, deducing from Foo<Q, alias<F>>
and Foo<1, T> that Q=1 and T=alias<F>=F, thus generating
<template F>
Bar(F&&) -> Bar<1, F>;
if this was correct syntax. */
int main() {
Bar f{ 5 };
}
如果我现在创建一个别名来显式指定以前不可推导的参数,as far as I understand,这个别名的隐式生成推导指南应该能够完全推导两个模板参数(根据标准模板参数推导规则),即使在定义 class 模板中未推导一种类型。
但是不使用alias
,而是使用alias2
的场景怎么办,即将推导指南改成
template <int Q, typename F>
Foo(F&& f) -> Foo<Q, typename alias2<F>::type>;
根据 documentation, this would now introduce a non-deduced context (as the template parameter appears left to a scope operator ::
), so the template argument deduction for T=F
should fail (which apparently does).
问题一:如果这个理论是正确的,我能做些什么吗?假设我不想使用一个普通的身份别名,而是一个更复杂的类型转换,最终将在推导指南中具有 typename transformation<Input>::result
的形状。
问题 2:即使现在,当我完全删除 Q 时,我的理论仍然失败,因为以下代码将被接受(GCC-10/11):
template <typename T>
struct Foo {
template <typename F>
Foo(F&&) { }
};
template <typename T>
struct alias2 { using type = T; };
template <typename F>
Foo(F&& f) -> Foo<typename alias2<F>::type>;
template <typename T>
using Bar = Foo<T>;
template <typename T>
void some(typename alias2<T>::type) { }
int main() {
Bar f{ 5 };
}
为什么编译器能够从 F 推导出 T,即使这是一个非推导上下文?
为了做你想做的事,C++ 必须能够反转图灵完备的子程序。
图灵完备程序不仅不可逆,而且无法确定给定的图灵完备程序是否可逆。您可以定义可逆的子语言,但这些子语言缺乏图灵完备的能力。
推导 Bar
别名参数:
template <typename T>
using Bar = Foo<1, T>;
需要反转第二个模板参数 alias<F>
才能找到 F
。当 alias
是普通模板别名时,这是可能的、允许的,并且是 C++ 标准所要求的。
当alias2
评估为foo<F>::type
时,这样的构造能够进行图灵完备计算。 C++ 标准没有让编译器 try 反转这样的计算,而是统一说“不要尝试”。它通常使用“依赖类型”来阻止这种反转尝试。
在你的第二种情况下,Bar
是 Foo
的普通别名。 Foo
有推导指南。该推导指南告诉我们如何从 F
到 T
,因此编译器不必反转任何可能的图灵完备程序来确定 T
.
C++ 语言有一堆措辞允许只是重命名参数等的模板别名就像它们是原始类型一样。本来连个玩具的别名都会挡住一堆这样的推演;但后来发现这是一个糟糕的计划。所以他们在标准中添加了文本来描述什么样的模板别名在那里“微不足道”,并修改了推导规则的措辞,以便将它们视为透明。
为了在类型推导期间反转任意图灵完备程序(事实上,几乎任何结构上非平凡的类型转换),您必须明确给出反转。
一旦你接受了这一点,它就变成了与语法的斗争,而不是概念上的斗争。
我不知道别名模板的自定义模板推导指南的现状。上次听说不支持,不过最近没看。