2024-03-12 11:45阅读: 56评论: 0推荐: 0

c++20 模板约束

concept

c++20 中,提案许久的 concept 被加入到标准中了,这也意味着不用再写恼人的 SFINAE 了(除非你是一个受虐狂,喜欢对着一堆报错中找到错误的位置)。

c++20 之前

c++20 之前,如果需要对模板实参进行编译期检查,只能使用 SFINAE ,或者是部分使用 c++17 添加的 if constexpr 进行编译期检查。

SFINAE : Substitution Failure Is Not An Error ,替换失败不是错误,也就是在对函数模板的重载决议中,如果在模板形参替换为显示指定的类型或者是推导出的类型失败时,那么这个推导将被丢弃,并不会导致最终的编译失败。

假设我们需要实现一个输出函数,这个函数使用 std::cout ,将任何传入的参数输出,我们先看看一个基础写法是怎么写的。

template <typename T>
auto WriteLine(const T &val) -> void {
std::cout << val << '\n';
}

很简单,对吧?问题来了,我们要怎么知道 std::cout << val << '\n' 中是否可以将 T 输出?一个简单的方式是写入,然后编译,结果自然是观察编译是否会出错。然而,如果你真的这么做了,就会得到一长串的 operator<< 错误,这实际上是标志着所有的重载函数都无法匹配上你所输入的类型,编译器不知道匹配哪一个,所以全部报错了。

SFINAE 怎么写?我们现在需要请出 std::enable_ifstd::declval 两位佬了。std::enable_ifSFINAE 的核心,如果满足约束条件,那么将得到一个确定的类型,否则没有类型,使得模板实例化失败。现在我们看看该怎么写。

template <typename T>
auto WriteLine(const T &val)
-> typename std::enable_if<std::is_same<decltype(std::cout << val), std::ostream&>::value, void>::type {
std::cout << val << '\n';
}

哦天,后面一长串是什么?这里面使用了 std::is_same 判断两个类型是否是一样的,然后使用 decltype 推断 std::cout << val 的返回类型。由于 std::cout << val 输出后的返回类型为 std::ostream& ,如果它能够有匹配的输出,那么最终一定会成功,std::enable_ifvoid 作为 type ,否则这里 std::is_same 实例化将失败,也带着 std::enable_if 实例化失败,最终使得没有匹配的 WriteLine

如果你现在编译一下,会发现现在报错不会太多了,那么这解决我们需要的编译报错问题了吗?解决了,但是你需要花费巨大心智在模板上雕出一朵花,更不用说这只是一个简单的场景了,如果这个函数具有几个模板形参,每个参数都需要很多个约束类型,最终 SFINAE 会往可读性的地狱发展。

concept 和 requires

c++20 及其以后,我们可以不用再写 SFINAE 的代码形式了,只需要考虑 conceptrequires

我们使用这两个来对刚刚的丑陋东西重写一遍。

template <typename T>
requires requires(const T val) { std::cout << val; }
auto WriteLine(const T &val) -> void {
std::cout << val << '\n';
}

至少可读性好多了,上面的 std::enable_if 很难看出与 std::cout << val 的关系,可是 concept 呢?

template <typename T>
concept OutputAble = requires(const T val) {
std::cout << val;
};
template <typename T>
requires OutputAble<T>
auto WriteLine(const T &val) -> void {
std::cout << val << '\n';
}

然而实际上,这样还是多余了点,我们可以更简单点。

template <OutputAble T>
auto WriteLine(const T &val) -> void {
std::cout << val << '\n';
}

这样是不是直接看懂了模板需要的类型满足是什么了,对吧?但是这还能再简单。

auto WriteLine(const OutputAble auto &val) -> void {
std::cout << val << '\n';
}

直接 auto ,那么现在就变得非常简单了,我们连类型都不关心了,只关心输入的这玩意是否满足可以被输出,这就是 conceptrequires 带来的能力。

那么我们看看 conceptrequires 是什么?

template <typename T>
concept OutputAble = requires(const T val) {
std::cout << val;
};
auto main() -> int {
std::cout << std::boolalpha << OutputAble<int> << '\n';
}

它输出了 true 。实际上,它有点像一个模板变量,当条件满足时为 true ,否则为 false ,那么条件在哪?

requires(const T val) { std::cout << val; }

这就是它所满足的条件,它需要一个 const T val 的变量,表示检查的操作一定不会修改到 val ,然后在 {} 中,它检查了 std::cout << val 是否是合法的,这显然比 std::enable_if 自然多了。const T val 只在编译期中存在,因此完全不用担心会影响到性能,只需要等到

实际上,我们刚刚一步一步简化的,就是 concept 的一些用法,我们可以使用 concept 先抽象出一些 requires 语句,然后复用它,或者直接在模板中使用 requires 来约束需要满足的 concept ,并且这个过程是可以再组合的,不用再写晦涩难懂的 std::enable_if 了。

本文作者:FlandreScarlet

本文链接:https://www.cnblogs.com/FlandreScarlet/p/18067975

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   フランドール·スカーレット  阅读(56)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起
  1. 1 Scarborough Fair 山田タマル
  2. 2 Faster Than Light Paradox Interactive,Andreas Waldetoft
  3. 3 世界は可愛く出来ている 上海アリス幻樂団
Scarborough Fair - 山田タマル
00:00 / 00:00
An audio error has occurred, player will skip forward in 2 seconds.

作词 : Pual Simon

作曲 : Pual Simon

作词 : イングランド民謡

作曲:加藤达也

Are you going to Scarborough Fair?

Parsley, sage, rosemary and thyme

Remember me to one who lives there

He once was a true love of mine

Tell him to make me a cambric shirt

Tell him to make me a cambric shirt

Parsley, sage, rosemary and thyme

Without no seams nor needlework

Then he'll be a true love of mine.

Tell him to find me an acre of land

Tell him to find me an acre of land

Parsley, sage, rosemary and thyme

Between the salt water and the sea strand

Then he'll be a true love of mine

Are you going to Scarborough Fair?