使用 concepts检查语法和语义约束
-
语法约束意味着我们可以在编译时检查某些需求是否得到满足(是否支持特定的操作?或特定的操作是否产生特定的类型?).
-
语义约束是指满足了某些需求,只能在运行时进行检查(某个操作是否具有相同的效果?或对特定值执行相同的操作总是产生相同的结果吗?).
语义约束的一个简单例子是concepts std::invocable和std::regular invocable之间的区别。
后者保证不修改传递的操作和传递的参数的状态。
我们不能用编译器检查,因此不同的文档说明了指定API的意图。
通常,对于sim- plicity,只使用std::invocable。
另一个例子是,除了某些句法差异之外,弱增量concepts和可增量concepts之间还有语义差异
-
incrementable要求每一个相同值的增量都给出相同的结果。
-
当满足incrementable时,您可以从一个起始值在一个范围内迭代多次。
-
当只满足弱增量时,你只能在一个范围内迭代一次。具有相同凝视值的第二次迭代可能产生不同的结果。
这个区别对迭代器很重要:输入流迭代器(从流中读取值的迭代器)只能迭代一次,因为下一次迭代会产生不同的值。因此,输入流迭代器满足弱可增量concepts,但不满足可增量concepts。然而,这些concepts不能用来检查这种差异
std::weakly_incrementable<std::istream_iterator<int>>
std::incrementable<std::istream_iterator<int>>
原因是这种差异是语义上的约束,不能在编译时检查。所以这些concepts可以用来记录约束条件
template<std::weakly_incrementable T>
void algo1(T beg, T end);
template<std::incrementable T>
void algo2(T beg, T end);
注意,这里的算法使用了不同的名称。由于我们无法检查约束的语义差异,程序员可以自行决定是否传递输入流迭代器
algo1(std: :istream_iterator<int>{std::cin}, //OK
std::istream_iterator<int>{});
algo2(std::istream_iterator<int>{std::cin}, //OOPS: violutes construint
std::istream_iterator<int>{});
但是,您不能基于此差异在两个不同的实现之间进行分派
template<std::weakly_incrementable T>
void algo(T beg, T end);
template<std::incrementable T>
void algo(T beg, T end);
如果在这里传递输入流迭代器,编译器将错误地使用多通道实现
algo(std: :istream_iterator<int>{std::cin},
std::istream_iterator<int>{});
幸运的是,这里有一个解决方案,因为对于这个语义差异,c++ 98已经引入了迭代器特征,迭代器concepts使用了这些特征。如果使用这些concepts(或相应的范围concepts),一切都可以正常工作
template<std: : input_iterator T>
void algo(T beg, T end);
template<std::forward_iterator T>
void algo(T beg,T end);
algo(std::istream_iterator<int>{std::cin},
std::istream_iterator<int>{});
因此,在这种情况下,应该使用更具体的迭代器和范围concepts。
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 N 天,点击查看活动详情”