[英]C++20: Validate Template Bodies Against Concepts
C++20 引入了一些概念,它允许我们在模板的声明中指定模板参数必须提供某些功能。 如果使用不满足约束的类型实例化模板,则编译将在实例化时失败,而不是在编译模板主体并在替换后注意到无效表达式时。
这很好,但它引出了一个问题:有没有办法让编译器在实例化之前查看模板主体(即,将其视为模板而不是模板的特定实例化),并检查所有表达式涉及模板参数的约束是否保证存在?
例子:
template<typename T>
concept Fooer = requires(T t)
{
{ t.foo() };
};
template<Fooer F>
void callFoo(F&& fooer)
{
fooer.foo();
}
这个概念阻止我使用不支持模板主体内的表达式的类型来实例化callFoo
。 但是,如果我将功能更改为:
template<Fooer F>
void callFoo(F&& fooer)
{
fooer.foo();
fooer.bar();
}
如果我使用定义foo
(因此满足约束)但不是bar
的类型实例化callFoo
,这将失败。 原则上,该概念应该使编译器能够查看此模板并在实例化之前拒绝它,因为它包含表达式fooer.bar()
,约束不保证存在。
我认为这样做可能存在向后兼容性问题,尽管如果此验证仅使用受约束的参数(不仅仅是typename
/ class
/等参数)完成,它应该只影响新代码。
这可能非常有用,因为由此产生的错误可用于指导约束的设计。 编写模板实现,编译(还没有实例化),然后在每个错误上,将所需的任何要求添加到约束中。 或者,在相反的方向,当遇到错误时,调整实现以仅使用约束提供的内容。
是否有任何编译器支持启用此类验证的选项,或者是否有计划在任何时候添加此选项? 它是现在还是将来进行此验证的概念规范的一部分?
是否有任何编译器支持启用此类验证的选项,或者是否有计划在任何时候添加此选项? 它是现在还是将来进行此验证的概念规范的一部分?
不,不,也不。
您正在寻找的功能称为定义检查。 也就是说,编译器根据提供的概念在模板的定义点检查模板的定义,如果有任何不验证的地方,就会发出错误。 例如,Rust Traits、Swift 协议和 Haskell 类型类就是这样工作的。
但是 C++ 概念不是那样工作的,而且考虑到 C++ 概念可以是任意表达式而不是函数签名(就像在其他语言中一样),添加对此类事物的支持似乎完全不可行。
你能做的最好的事情就是用尽可能少的满足你要求的外来类型对你的模板进行彻底的单元测试(这里的术语是archetype )并希望得到最好的结果。
TL;DR:不。
原始 C++11 概念的设计包括验证。 但是当它被放弃时,新版本的设计范围要窄得多。 新设计最初是基于constexpr
布尔条件构建的。 添加了最终的requires
表达式以使这些布尔检查更易于编写并为概念之间的关系带来一些理智。
但是 C++20 概念设计的基础使得它基本上不可能进行完全验证。 即使一个概念完全是由原子requires
表达式构建的,也没有办法真正判断一个表达式在代码中的使用方式是否与它在requires
表达式中的使用方式完全相同。
例如,考虑这个概念:
template<typename T, typename U>
concept func_to_u = requires(T const t)
{
{t.func()} -> std::convertible_to<U>;
};
现在,让我们想象以下模板:
template<typename T, typename U> requires func_to_u<T, U>
void foo(T const &t)
{
std::optional<U> u(std::in_place, t.func());
}
如果您查看std::optional
,您会发现in_place_t
构造函数不采用U
。 那么......这是对该概念的合法使用吗? 毕竟,这个概念说受这个概念保护的代码将调用func()
并将结果转换为U
。 但是这个模板不这样做。
它取而代之的是返回类型,实例化一个不受func_to_u
保护的模板,并且该模板做它想做的任何事情。 现在,事实证明这个模板确实对U
执行了转换操作。
所以一方面,很明显我们的代码确实符合func_to_u
的意图。 但这只是因为它碰巧将结果传递给了符合func_to_u
概念的其他函数。 但是该模板不知道它受到convertible_to<U>
的限制。
那么......编译器应该如何检测这是否正常? 失败的触发条件将在optional
的构造函数中的某个地方。 但是那个构造函数不受这个概念的约束; 这是我们的外部代码受制于这个概念。 所以编译器基本上必须解开你的代码使用的每个模板,并将这个概念应用到它上面。 只是它甚至不会应用整个概念; 它只是应用convertible_to<U>
部分。
这样做的复杂性很快就会失控。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.