跟我学c++中级篇——concepts的几个应用

一、concepts的入门应用

concepts的应用是一个非常必要的问题。它对于模板在实际编程中的友好性有着至关重要的作用。先从最简单的一个示例说起:

 struct PlusSum
 {
     int d_ = 9;
     double a_ = 11;
 };

 template <typename T,typename N>
 concept C2 = requires(T t, N n) {
     {t + n}-> std::same_as<T>;//要求t+n是T类型
 };
 template <typename T, typename N>
 concept C1 = requires(T t, N n) {
     {t + n}-> std::convertible_to<T>;//要求t+n可转成T类型
 };
 template<typename T,typename N>
 T Sum(T t, N n) requires C2<T,N>
 {
     return t + n;
 }

int main()
{
    int d = 10;
    double a = 9;

    //在这种情况double+int=>double所以C2不满足
    int r = Sum(d ,a);
    std::cout << "sum value:" << r << std::endl;

    //无法满足约束
    r = Sum(PlusSum{}, PlusSum{});
    std::cout << "sum value :" << r << std::endl;
}

如果在这个函数里把概念部分先去除,编译器只会报“未匹配的重载函数”。如果把C1打开,则会报约束无法满足,但此时只是第个Sum有问题,第一个没有问题。如果再把C2打开,把C1注释,会发现两个Sum都说不满足约束。换句话说,两个约束一个比一个更严格,所以在某些场景下,对类型要求严格时,可以做类似的判断。
在这样一个简单的例程中,当然显示不出来约束的巨大优势。毕竟就几行代码,一扫而过也能找到错误。但是如果在实际工程中,不断嵌套调用,如何一下子定位出来呢?甚至像那些非严格自动转化的类型引起的警告导致的错误,可能会让开发者寻找一天。这时候儿用约束的优势就明白的显现出来了。

二、更进一步的应用

在上面只是简单的使用了一下requires,下面综合的对一些成员函数,CV限定等进行控制,看一下代码:

//函数限制
 template<typename T>
 concept func_ask = requires(T t)
 {
     t.must();
 };

 class A {};
 class B
 {
 public:
     void must() const {}
 };
 class C : public A
 {
 public:
     void must(const std::string& s = "test") const { std::cout << s << std::endl; }
 };
 //下面的继承改成私有亦会报错
 class D :public B//private B
 {
 public:
     D() {};
     ~D() {};

 private:

 };

 template <typename T>
 concept is_const_ok = !std::is_const_v<T>;
 template <typename T>
 concept is_size_ok = requires (T t)//有size()函数且返回值可转size_t
 {
     {t.size()}->std::convertible_to<std::size_t>;
 };

 template<typename T,typename N>
 concept cs1 = requires (T t,N n)
 {
     {t.Get(n)}->std::same_as<std::string>;
 };
 template <typename T>
 class OkTest
 {
 public:
     /*int*/std::string Get(int d) { std::cout << "is test" << std::endl; return ""; }//返回值限制,可以试一下
 };
//带参数限制
 template <typename T,typename N> 
 requires cs1<T,N>
 void TestOK(T t,N n)
 {
     t.Get(n);
 }

 template<typename T>
 concept cs = requires (T t)
 {
    // {t}->std::same_as<std::string>;//约束通不过,估计是引用常量啥的问题
     {std::decay_t<T>(t)}-> std::same_as<std::string>;
 
     //requires requires {std::is_same_v<decltype(t), decltype("123")>; };//这个约束也可以,不过有点LOW
     
     //t.size() < 6;//这个约束没有用,可以考虑一下为什么
 };
 template <typename T>
 concept cf = requires (T t)
 {
     std::is_function_v<decltype(t) >; 
 };

 template <cs T>
 auto DataGet( T &t)
 {
     return t;
 }

 template<typename T, cf F >
 class MyConceptsExample
 {
 public:
     MyConceptsExample() {}
     ~MyConceptsExample() {}
     template <cs U> 
     U SetName(U t)
     {
         s_ = t;
         std::cout << "get name is:" << s_ << std::endl;
         return s_;
     }
     int size() 
     {
         return 10;
     }

     std::string s_ = "";
     F f_;
 };


 template<func_ask T>
 requires is_const_ok<T>  //简化了std::enable_if的应用,引用永远是flase=>true
 void must(const T& t)
 {
     t.must();
 }
 template<typename T>
 requires is_size_ok<T>
 auto createEx(T& t)
 {
     std::cout << "size value is:" << t.size() << std::endl;

 }
#include<functional>
 typedef void(*PF)(int);
 void IsFunc(int d)
 {
     std::cout << d << std::endl;
 }

 void  TestMyConcepts()
 {
     OkTest<int > ok;
     TestOK(ok,10);
     std::string s = "123";
     int d = 0;
     DataGet(s);//换成d,就会报约束问题

     MyConceptsExample<int, std::function<void(int)>> my;
     MyConceptsExample<int, PF> my1;
     // my.SetName(s);
     createEx(my);
     //must(A()); // 未满足关联约束
     must(B());
     must(C());
     must(D());
     return;
 }

int main()
{
    TestMyConcepts();
    return 0;
}

基本按上面的代码,就可以把需要概念应用上来。这样,在实际开发中,对一些细节的掌控就可以通过概念来控制。比起以前的SFINAE要简单方便易理解多了。特别是在c++20中好多原来SFINAE中的技巧都可以直接使用了,所以说技术还是得进步,光靠技术人员玩一些花活,不是发展之道。解决一时之需还可,长久来看,反而对c++语言的发展有碍。

三、更高的一些用法

分析完了一些基本的用法,来看一些高级的用法。
1、模板的特化
在模板的使用中,使用了concepts后,模板的特化和偏特化不受影响,但在此过程中必须符合约束的要求:

#include <concepts>
#include <string>
#include <type_traits>

template <std::signed_integral T>
class NewData
{
public:
    T GetData(T t) { return t; }
};
template <>
class NewData<long> {};
//template <>
//class NewData<std::string> {};//不满足约束

template <std::signed_integral T,typename U>
requires std::is_const_v<U>
class NewDataConst
{
public:
    T GetData(T t,U u) { return t; }

};
template<typename U>
class  NewDataConst<int, U> {};
template<typename U>
class  NewDataConst<double, U> {};
template<typename T>
class  NewDataConst<T,const  int> {};
template<typename T>
class  NewDataConst<T, const std::string> {};
 int main()
 {
     NewData<int> nd;
     //NewData <float> nd1;//不满足约束
     //NewData <std::string> nd2;//不满足约束
     NewData<long> nd3;

    // NewDataConst<double, int> ndc;
     NewDataConst<int, const double> ndcc;//double换成 std::string会有歧义
    // NewDataConst<int, int> n;

   return 0;
 }

上面的代码会发现,全特化时约束立刻起作用,而偏特化要等到定义应用时才会起作用。当然concepts在一些情况下也可以特化模板:

 template <typename T>
 class Example 
 {
 public:
     Example() { std::cout << "stand " << std::endl; }
 };

 template <std::integral T>
 class Example<T> 
 {
 public:
     Example()  { std::cout << "no stand " << std::endl; } 
     };

 void TestExample() 
 {
     Example<int> t1; // no stand
     Example<std::string> t2; //  stand
 }

int main()
{
    TestExample();
    return 0 ;
}

2、函数重载
那么如果有concepts的限制,函数重载有什么影响么?

 void funcall(int d)
 {
     std::cout << "call stand func" << std::endl;
 }
 template<typename T>
 void funcall(T t)
 {
     std::cout << "call template func" << std::endl;
 }
 template<std::integral T>
 void funcall(T t)
 {
     std::cout << "call concept template func" << std::endl;
 }
 template <>
 void funcall<double>(double d)
 {
     std::cout << "call all template func" << std::endl;
 }
int main()
{
    int d = 0;
    funcall(d);
    funcall("test");
    funcall<>(1.1);
    funcall<int>(1);
    return 0;
}

结果:

call stand func
call template func
call all template func
call concept template func

通过上面的代码可以看到其实concepts是不影响函数重载的。

四、总结

古人形容朝代的兴亡用“其成也勃焉,其亡也忽焉”。其实,一个语言的兴衰也是如此。js 和python为什么能够迅速起来,简单是第一位。不要提什么功能强大,那都是基于简单的前提。所以c++必须得走一条在尽可能范围的情况下,把编写c++代码的难度降下来。这个不是谁说了算的问题,是涉及到一个语言能否存在的重要前提。
让我们拭目以待c++23以后的新标准会有什么更大惊喜出来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值