c++20 concepts的理解

这篇具有很好参考价值的文章主要介绍了c++20 concepts的理解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

c++20 concepts的理解

concepts在c++20中被引入,其作用是对模板参数进行约束,极大地增强了c++模板的功能。

在c++20之前,如果希望获取类似的效果,使用起来并不方便。

没有concept时,如何实现对模板参数进行约束?

static_assert

我们可以使用static_assert去对模板类型T进行约束。如下所示:

#include <type_traits>
#include <iostream>

template<class T>
void test(T a)
{
    static_assert(std::is_integral<T>());
    std::cout << "T is integral" << std::endl;
}
int main()
{
    test(10);
    test<double>(12.3); 
}

但是该种方法不太好,因为需要将static_assert嵌入到函数的内部,这意味着即使类型不对,模板还是成功的匹配上了,只是在模板函数内部展开时出现编译错误。

SFINAE

SFINAE是Substitution Failure Is Not An Error的缩写,翻译过来的意思是替换失败并不是一个错误。

SFINAE模板元编程中常见的一种技巧,如果模板实例化后的某个模板函数(模板类)对该调用无效,那么将继续寻找其他重载决议,而不是引发一个编译错误。

因此一句话概括SFINAE,就是模板匹配过程中会尝试各个重载模板,直到所有模板都匹配失败,才会认为是真正的错误。

例如下面这个经典的例子:

struct Test {
     typedef int foo;
};

template <typename T>
void f(typename T::foo) {}  // Definition #1

template <typename T>
void f(T) {}  // Definition #2

int main() {
    f<Test>(10);  // Call #1.
    f<int>(10);   // Call #2. Without error (even though there is no int::foo)
                // thanks to SFINAE.
}

f<Test>(10)最终将使用到第一个模板定义, 而f<int>(10)最终将使用到第二个模板定义。

SFINAE 原则最初是应用于上述的模板编译过程。后来被C++开发者发现可以用于做编译期的决策,配合sizeof可以进行一些判断:类是否定义了某个内嵌类型、类是否包含某个成员函数等。例如STL中迭代器中的has_iterator_category。

template <typename T>
struct has_iterator_category {
    struct two { char a; char b; };

    template <typename C>
    static two& test(typename C::iterator_category*);

    template <typename>
    static char& test(...);

    static const bool value = sizeof(test<T>(nullptr)) == sizeof(two);
};

enable_if

enable_if的出现使得SFINAE使用上更加方便,进一步扩展了上面has_xxx,is_xxx的作用。而enable_if实现上也是使用了SFINAE。

enable_if的定义简单, 即当_Test是true时,将不会有type的类型定义,而当_Test是false时,将会有type的类型定义。

// STRUCT TEMPLATE enable_if
template <bool _Test, class _Ty = void>
struct enable_if {}; // no member "type" when !_Test

template <class _Ty>
struct enable_if<true, _Ty> { // type is _Ty for _Test
    using type = _Ty;
};

下面是利用enable_if去实现SFINAE的方式。

#include <type_traits>
#include <iostream>

template <typename T>
typename std::enable_if<std::is_integral<T>::value, T>::type 
test(T value)
{
    std::cout<<"T is intergal"<<std::endl;
    return value;
}

template <typename T>
typename std::enable_if<!std::is_integral<T>::value, T>::type 
test(T value)
{
    std::cout<<"T is not intergal"<<std::endl;
    return value;
}

int main()
{
    test(100);
    test('a');
    test(100.1);
}

可以看到SFINAE的主要实现了is_xxx和has_xxx的语义,但是其语法并不简单,对使用者有较高要求,并且即便写正确了,可读性也相对较差。

有了concept之后如何使用?

声明concept

声明一个concept的语法如下所示:

template < template-parameter-list >
concept concept-name = constraint-expression;

例如,约束T是一个整形:

template <typename T>
concept integral = std::is_integral_v<T>;

也可以使用requires更加灵活地定义concept,例如下面的例子要求T类型拥有一个名字叫做print的方法,另外需要拥有一个toString方法,并且返回值是string类型。

template <typename T>
concept printable = requires(T t) {
    t.print(); //1
    {t.toString()} -> std::same_as<std::string>; //2
};

使用concept

使用concept有三种方式:

方法1:直接将concept嵌入模板的类型的尖括号<>

template<Arithmetic T> 
void f(T a){/*function definition*/};
we can enforce it just after template declaration using requires:

方法2:在模板声明的下方使用requires关键字

template<class T> 
requires Arithmetic<T>
void f(T a)
{/*function definition*/};
or after the function declaration

方法3:使用后置形式,直接在函数声明的后方使用requires关键字添加约束。

#include<concepts>
template<class T>
void f(T a) requires integral<T> // integral is in header <concepts>	
{/*function definition*/}; 

此外,concept还可以使用逻辑运算符 && 和 ||。例如:

template <class T>
concept Integral = std::is_integral<T>::value;

template <class T>
concept SignedIntegral = Integral<T> && std::is_signed<T>::value;

template <class T>
concept UnsignedIntegral = Integral<T> && !SignedIntegral<T>;

下面是两个完整的例子来看看concepts是如何实现了is_xxxhas_xxx的功能。

第一个例子中test模板T只能是整形或者是double。实现了is_xxx。

#include <iostream>

template <class T>
concept Integral = std::is_integral<T>::value;

template <class T>
concept IsDouble = std::is_same<T, double>::value;


template<Integral T>
void test(T a)
{
    std::cout << "test(int) functin called" << std::endl;
}

template<IsDouble T>
void test(T a)
{
    std::cout << "test(double) functin called" << std::endl;
}

int main()
{
    test(100);
    test(10.0);
}

第二个例子中print函数要求t拥有print函数。实现了has_xxx。文章来源地址https://www.toymoban.com/news/detail-484536.html

#include <iostream>

template <typename T>
concept Has_print = requires(T t) {
    t.print(); //1

};

class HasPrint
{
public:
    void print(){};
};

class NoPrint
{

};

template<Has_print T>
void print(T t)
{
    t.print();
}

int main()
{
    HasPrint t;
    print(t);

    NoPrint t2;
    print(t2); //将报编译错误
}

总结

  • c++20的concepts增强了模板对于参数类型约束的功能,语法简单,可以提高代码的可读性。
  • 如果你的项目不能使用较新的标准,那么还是要老老实实的使用SNINAE,enable_if那一套东西。

到了这里,关于c++20 concepts的理解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 深入理解作用域、作用域链和闭包

     ​ 🎬 岸边的 风 :个人主页  🔥  个人专栏  :《 VUE 》 《 javaScript 》 ⛺️  生活的理想,就是为了理想的生活 ! ​ 目录  📚 前言  📘 1. 词法作用域 📖 1.2 示例 📖 1.3 词法作用域的应用场景  📘 2. 作用域链 📖 2.1 概念 📖 2.2 示例 📖 2.3 作用域链的应用场景  📘

    2024年02月10日
    浏览(41)
  • 微信小程序引入Vant Weapp修改样式不起作用,使用外部样式类进行覆盖

            在项目中使用第三方组件修改css样式时,总是出现各种各样问题,修改的css样式不起作用,没有效果,效果不符合预期等。 栗子(引入一个搜索框组件) 实现效果:  左侧有一个搜索文字背景为蓝色,接着跟一个搜索框 wxml wxss emmm...明明我们css都写了,这出现的是什么鬼   审查

    2024年02月02日
    浏览(56)
  • 作用域链的理解

    🍿🍿🍿作用域,即变量(变量作用域又称上下文)和函数生效(能被访问)的区域或集合 作用域决定了代码块中变量和其他资源的可见性 我们一般将作用域分成: 我们一般将作用域分成: 全局作用域 函数作用域 块级作用域 🧂🧂🧂任何不在函数中或是大括号中声明的变

    2024年02月08日
    浏览(48)
  • react钩子副作用理解

    useEffect(() = { fetch(‘https://api.example.com/data’) .then(response = response.json()) .then(data = setData(data)); }, []); 怎么理解这个[] 在 React 中,useEffect 钩子用于处理副作用,比如数据获取、订阅、手动 DOM 操作等。useEffect 接受两个参数:一个是副作用函数,另一个是依赖数组。 在你提供的代

    2024年02月13日
    浏览(33)
  • 【无标题】作用域的理解

    作用域,即变量(变量作用域又称上下文)和函数生效(能被访问)的区域或集合,作用域决定了代码区块中变量和其他资源的可见性。 函数myFunction内部创建一个inVariable变量,在全局访问这个变量的时候,系统会报错,这就说明在全局是无法获取到(闭包除外)函数内部的

    2023年04月10日
    浏览(36)
  • web中为什么要引入service层以及前端控制器DispatchServlet的作用以及原理剖析

    review: 最初的做法是: 一个请求对应一个Servlet,这样存在的问题是servlet太多了 把一些列的请求都对应一个Servlet, IndexServlet/AddServlet/EditServlet/DelServlet/UpdateServlet - 合并成FruitServlet 通过一个operate的值来决定调用FruitServlet中的哪一个方法 使用的是switch-case 在上一个版本中,Ser

    2024年02月04日
    浏览(41)
  • js中作用域的理解?

    作用域,即变量(变量作用域又称上下文)和函数生效(能被访问)的区域或集合 换句话说,作用域决定了代码区块中变量和其他资源的可见性 举个例子 上述例子中,函数myFunction内部创建一个inVariable变量,当我们在全局访问这个变量的时候,系统会报错 这就说明我们在全局是无

    2024年02月11日
    浏览(37)
  • proto中service 作用的理解

    转载请注明出处: 在 proto 文件中,service 用于定义一组 RPC 方法,在服务端实现这些方法,并在客户端调用这些方法进行远程过程调用。 service 的定义方式如下: 其中,MyService 是服务的名称,MyMethod 是方法的名称,MyRequest 和 MyResponse 分别是输入和输出消息的类型。 在 Java

    2024年02月05日
    浏览(46)
  • Bug:elementUI样式不起作用、Vue引入组件报错not found等(Vue+ElementUI问题汇总)

    1. Vue引用Element-UI时,组件无效果解决方案 前提: 已经安装好elementUI依赖 如果此时发现element的组件依然没起效果,原因:未引入css样式文件 当前效果: 预期效果: 解决办法:在main.js中引入css文件 2. Vue引入components报错:export xxx not found 在pages文件夹的Register.vue页面中引入

    2024年02月07日
    浏览(43)
  • Go语言代码块与作用域理解

    如果不深入理解 Go 语言的代码块作用域,程序将产生我们无法理解的行为,比如说在循环中创建 goroutine func, 为什么需要传递参数至 goroutine 内部,否则所有的 func 使用的变量参数都是循环的最后一个值。 看下边这个 demo, 就需要深入理解 Go 语言代码块的作用域才能理直气壮

    2024年02月11日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包