使用 concepts 检查语法和语义约束

571 阅读2分钟

使用 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 天,点击查看活动详情