C++ 20 Concept 初步认识

模板元编程具有“类型参数”,就是template<typename T>这句话中T, 就是类型形参。非常多的场景下,对T是有要求(requirement)的,或者说有限制(constraint)。例如:

template<typename T>
void foo( T a  ){
    (a & 0x0F);
}
int main(){
    foo(3.14); //编译错误:浮点数不能做位运算。
}

由此看来,不是随便传什么类型给foo的。复杂代码里,编译器的错误提示如天书一般,这是让模板编程让很多程序员望而却步的原因。另一方面,为了让重载函数foo更有适用性,引入了SFINE技术。提供多个foo重载版本,引导编译器选择最优的重载函数。

template<typename T>
struct is_integer;

template<>
struct is_integer<int>{
    typedef int type;
};

template<typename T>
struct is_float;

template<>
struct is_float<float>{
    typedef float type;
};

template<>
struct is_float<double>{
    typedef double type;
};

template<typename T>
T foo2( T a, typename is_integer<T>::type* = 0  ){
    return (a & 0x0F);
}

template<typename T>
T foo2( T a, typename is_float<T>::type* = 0  ){
    return a;
}

int main()
{
    foo2(3);
    foo2(3.14f);
    foo2(3.14);
}

这种技巧广泛应用多年。简言之,就是根据tag做分派。这样看来,函数的接口描述部分,特别是对类型的需求部分,需要语言级别的支持和扩充。typename is_integer<T>::type* = 是借用了函数的默认参数机制来做接口描述,是一种workaround不得已而为之的选择,显然函数在业务代码里不需要这个空指针。C++20引入的Concept机制,综合解决一切问题。 

template<typename T>
struct is_integer;

template<>
struct is_integer<int>{
    typedef int type;
};

template<typename T>
struct is_float;

template<>
struct is_float<float>{
    typedef float type;
};

template<>
struct is_float<double>{
    typedef double type;
};

template<typename T>
concept IsInteger = requires(T){
    typename is_integer<T>::type;
};

template<typename T>
concept IsFloat = requires(T){
    typename is_float<T>::type;
};

template<IsInteger T>
T foo3(T a){
    return (a & 0x0F);
}

template<IsFloat T>
T foo3(T a){
    return a;
}

int main()
{
    foo3(3);
    foo3(3.14f);
    foo3("3.14");  //编译错误,提示char*不满足Interger概念和Float概念
}

foo3函数对于类型T的需求更加明确,甚至对需求起了名字,让编译器认识。因此编译器会投桃报李,当违反concept描述的需求时,编译器提供友好的提示。

template<typename T>
constexpr bool large_than_3() { return sizeof(T) > 3; }
 
template<typename T>
    requires ( large_than_3<T>() && sizeof(T) < 5  )
void f1(T){} 

template<typename T>
concept MyCheck = requires(T){  large_than_3<T>() && sizeof(T) < 5; };

template<MyCheck T>
void f2(T){} 

template<typename T>
    requires MyCheck<T>
void f3(T){} 

int main() {
   f1(1);
   f2(1);
   f3(1); 
}

 

template <typename T>
concept IntOrFloat = std::is_integral_v<T> || std::is_floating_point_v<T>; 

template <IntOrFloat T>
void foo(T a)
{
    std::cout << a << std::endl;
}

void foo(const char* a){
    std::cout << a << std::endl;
}

int main() 
{
    foo(123);
    foo(123.0);
}

使用逻辑运算符,连接编译期bool常量。支持或||,与&&,非!。std::is_integral_v<T>是个bool变量模板,其值由模板参数T决定。在没有变量模板时,可以用std::is_integral<T>::value这样的元函数。

#include <type_traits>
#include <iostream>

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

Integral auto gcd(Integral auto a,Integral auto b){
    if(b==0)
        return a;
    else return
        gcd(b,a%b);
}

int main(){
    auto m = gcd(16,7);
    std::cout << m << std::endl;
    return 0;
}

这段代码也很有意思,concept配auto

#include <type_traits>
#include <iostream>

template<typename T>
concept have_value = requires(T a,int b) {
  a.name;     //应该具有成员
  a.get_value();  //应该具有get_value()函数
  a.get_value(b); //应该具有get_value(int)函数
  { a.set_value(b) } -> std::same_as<bool>;  //应该具有bool set_value(int)函数
};

struct A
{
  std::string name = "tony";
  int get_value() const{ return 1;}
  int get_value(int) const{ return 1;}
  bool set_value(int){ return true; }
};

template<typename T>
requires have_value<T>
auto show(const T& a) {

    std::cout << a.name << " " << a.get_value() << std::endl;
}

int main(){

    A a;
    show(a);
}

参考:
https://kheresy.wordpress.com/2019/12/16/c20-concepts-p2/
https://en.cppreference.com/w/cpp/language/constraints

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值