首页 > 其他分享 >模板约束介绍

模板约束介绍

时间:2023-03-19 14:01:04浏览次数:45  
标签:std Process 介绍 约束 ++ 表达式 template requires 模板


SFINE(substitution failure is not an error)

在模板编程中,SFINE是比较常见的一种特性,举个例子【1】:

template<typename T, unsigned int N>
std::size_t GetArrayLen(T(&)[N])
{
return N;
}

template<typename T, unsigned int N>
T::SizeType GetArrayLen(const T& t)
{
return t.Size();
}


int main()
{
int array[10];
GetArrayLen(array);
return 0;
}

在第二个版本函数中要求T中存在SizeType类型即:

struct Test {
using SizeType = int;
...
};

通过数组array进行模板实参替换时,编译器发现第二个函数模板实参替换后不符合要求(缺少T::SizeType),但不会报错而寻找其他匹配的函数,在所有候选者都不满足要求之后才会出现编译报错:no matching function for call GetArrayLen,配合std::enable_if进而过滤函数模板或者部分特化类模板;

We say "we SFINE out a function" if we mean to apply the SFINE mechanism to ensure that function templates are ignored for certain constraints by instrumenting the template code to result in invalid code for these constraints.

示例:判断类型是否支持前置自增和后置自增

方式一

namespace is_incrementable_
{
// a type returned from operator++ when no increment is found in the
// type's own namespace
struct tag {};
// any soaks up implicit conversions and makes the following
// operator++ less-preferred than any other such operator that
// might be found via ADL.
struct any { template <class T> any(T const&); };

tag operator++(any const&);
tag operator++(any const&,int);

// In case an operator++ is found that returns void, we'll use ++x,0
tag operator,(tag,int);

// two check overloads help us identify which operator++ was picked
char (& check_(tag) )[2];

template <class T>
char check_(T const&);


template <class T>
struct impl
{
static typename boost::remove_cv<T>::type& x;

BOOST_STATIC_CONSTANT(
bool
, value = sizeof(is_incrementable_::check_(BOOST_comma(++x,0))) == 1
);
};
template <class T>
struct postfix_impl
{
static typename boost::remove_cv<T>::type& x;

BOOST_STATIC_CONSTANT(
bool
, value = sizeof(is_incrementable_::check_(BOOST_comma(x++,0))) == 1
);
};

}

template<typename T>
struct is_incrementable :
public boost::integral_constant<bool, boost::detail::is_incrementable_::impl<T>::value>
{
};

template<typename T>
struct is_postfix_incrementable :
public boost::integral_constant<bool, boost::detail::is_incrementable_::postfix_impl<T>::value>
{
};

实现方式来自boost库【2】,在is_incrementable_::impl进行模板实参替换时:

1)x 支持x++或者++x,则逗号表达式最终为0,匹配至

template <class T>
char check_(T const&);

返回类型为char,则value = sizeof(char) == 1,为true;

2)x不支持时,则匹配到tag operator++(any const&);,其中any的构造函数能支持任意类型,返回为tag;

再匹配到逗号表达式重载函数声明tag operator,(tag,int);,逗号表达式最终为tag类型,此时check_有模板函数和普通函数都能满足匹配,编译器优先选择普通函数,即

char (& check_(tag) )[2];

得到value = sizeof(char(&)[2]) == 1,为false;

方式二

template<typename T, typename = std::void_t<>>
struct IsIncrementable : std::false_type {};

template<typename T>
struct IsIncrementable<T, std::void_t<decltype(++std::declval<T&>())>> : std::true_type {};

template<typename T, typename = std::void_t<>>
struct IsPostIncrementable : std::false_type {};

template<typename T>
struct IsPostIncrementable<T, std::void_t<decltype(std::declval<T&>()++)>> : std::true_type {};

template<typename T, typename = std::enable_if_t<IsIncrementable<T>::value && IsPostIncrementable<T>::value>>
void Process(T t)
{
std::cout << "T is incrementable " << std::endl;
}

struct TestInc {
TestInc& operator++()
{
return *this;
}
TestInc operator++(int)
{
return *this;
}
};

struct Test {
};

int main()
{
Process(TestInc {});
// Process(Test {}); not found
return 0;
}

Process对模板参数有约束,要求支持前置自增和后置自增,对于不支持自增操作的类型,IsIncrementable利用SFINE过滤掉了特化版本,选择了泛化版本,即继承了std::false_type;

相较于方式一中is_incrementable的实现有所简化;

方式三

C++20增加的4大特性之一,Constraits and concepts为模板编程带来了极大的便利,利用concepts实现:

template<typename T>
concept IsIncmentable = requires(T t) {
t++;
++t;
};

template<IsIncmentable T>
void Process(T t)
{
std::cout << "T is incrementable " << std::endl;
}

struct TestInc {
TestInc& operator++()
{
return *this;
}
TestInc operator++(int)
{
return *this;
}
};

struct Test {
};

int main()
{
Process(TestInc {});
// Process(Test {}); // not found
return 0;
}

进一步减少代码行数,Process函数模板可以修改为:

template<typename T> requires requires (T t) {t++; ++t;}
void Process(T t)
{
std::cout << "T is incrementable " << std::endl;
}

Constraits and concepts

​Class templates​​, ​​function templates​​, and non-template functions (typically members of class templates) may be associated with a constraint, which specifies the requirements on template arguments, which can be used to select the most appropriate function overloads and template specializations. 【3】

concept语法:

template<模板参数列表>
concept 概念名 = 约束表达式;

requires语法:

requires (形参列表) {
表达式;
}

示例:函数模板参数满足为指针类型的约束

template<typename T>
concept IsPointer = std::is_pointer_v<T>; // 概念约束模板参数为指针类型

以下四种语法表达均符合要求:

template<IsPointer T> // 按语境推导出的类型会隐式地用作第一个实参,即IsPointer<T>
void Process(T p) { }
template<typename T> requires IsPointer<T> // requires 子句
void Process(T p) { }
template<typename T>
void Process(T p) requires IsPointer<T> { } // requires 子句
void Process(IsPointer auto p) { }

合取(conjunction)与析取(disjunction)

合取与析取分别对应&&和||符号,在依次对每个约束进行检查时,首先检查表达式是否合法,若不合法则该约束不满足,否则进一步对约束进行求值判断是否满足【3】

template<typename T, typename U>
concept C = (std::is_integral_v<typename T::ValueType> || std::is_integral_v<typename U::ValueType>); // 析取表达式
struct MyType {
using ValueType = int;
};
static_assert(C<double, MyType>);

double没有ValueType,表达式不合法;而第二个表达式合法,所以约束为真;

template<typename T, typename U> 
concept C1 = bool(std::is_integral_v<typename T::ValueType> || std::is_integral_v<typename U::ValueType>); // 非析取表达式
static_assert(!C1<double, MyType>);

此处C2并不是析取表达式,首先检查整个表达式是否合法,double中没有ValueType则整个表达式为假;

template<typename ...T> // 非析取表达式
concept C1 = (std::is_integral_v<typename T::ValueType> || ...);

对于可变参模板的约束表达式,同样只要有一个类型没有ValueType,则整个表达式为假;

requires表达式

requires表达式的返回类型为bool,总结为:

template<typename T>
concept C = (traits) && (contexpr bool 值或函数) && (概念约束) &&
requires(形参列表) {
表达式;// 只检查表达式有效性
{ 表达式;} -> 返回值需要满足的概念约束
} &&
requires { // 嵌套
requires bool表达式; // 即检查表达式合法性也求值
}

嵌套实现中使用requires表达式时需要添加requires关键字

requires {
requires (形参列表) { 表达式;}; // compile error
}
修改为:
requires { // 嵌套
requires requires (形参列表) { 表达式;};
}

requires子句

关键词 requires 用来引入 requires 子句,它指定对各模板实参,或对函数声明的约束。 即上文示例中的函数模板参数约束:

template<typename T> requires IsPointer<T> // requires 子句
void Process(T p) { }

在上文判断类中支持自增的方式三实现中,即使用require + require表达式:

template<typename T> requires requires (T t) {t++; ++t;}
void Process(T t)
{
std::cout << "T is incrementable " << std::endl;
}

使用requires子句对函数模板约束时更加方便,示例:

template<typename T>
void Process(T) {} // 通用实现

template<typename T, typename = std::enable_if_t<sizeof(T) == 2>>
void Process(T) {}

上述实现中当调用Process((short)1);,两个函数模板在重载决议中会存在歧义报错;

template<typename T> requires (sizeof(T) == 2)
void Process(T) {}

template<typename T>
void Process(T) {}

修改为requires 子句约束后,则能选择到约束版本;

约束偏序

concept会对约束表达式中的各个约束进行规范化展开,直到剩下不可分割约束(原子约束);

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

template<typename T>
concept Con2Concept = std::is_integral_v<T> && sizeof(T) > 1;

void Process(Con1Concept auto t)
{
}

void Process(Con2Concept auto t)
{
}

上述代码中两个concept为原子约束,相互之间没有包含关系,因此在Process((int)0)编译时,两个函数都能满足,编译器无法决议使用哪个函数导致歧义,将上述代码修改为:

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

template<typename T>
concept Con1Concept = Con0Concept<T>;

template<typename T>
concept Con2Concept = Con0Concept<T> && sizeof(T) > 1;

此时Con1Concept的原子约束为Con0Concept,而Con2Concept包含Con0Concept,即Con2Concept包含Con1Concept,即Con2Concept更受约束;因此在Process((int)0)编译时,两个重载函数在进行决议时选择更受约束的版本;

注意事项

concept不 能 被 继 承

概念不能递归地提及自身

template<typename T>
concept V = V<T*>; // 错误:递归的概念

不 能 通 过 requires限 定 一 个 concept 【4】

template<class T>
concept C1 = true;
template<C1 T>
concept Error1 = true; // 错误:C1 T 试图约束概念定义
template<class T> requires C1<T>
concept Error2 = true; // 错误:requires 子句试图约束概念

不 能 通 过 new分 配

不 能 用 于约束 虚 函 数 (虚 函 数 不 支 持 模 板 )

参考资料

【1】C++ templates

【2】boost/detail/is_incrementable.hpp

【3】C++20高级编程

【4】​​https://en.cppreference.com/w/cpp/language/constraints​

标签:std,Process,介绍,约束,++,表达式,template,requires,模板
From: https://blog.51cto.com/u_13137973/6131201

相关文章

  • 线段树模板
    扫描线#include<bits/stdc++.h>#defineintlonglongusingnamespacestd;constintN=4e5+10;intread(){ intx=0,f=1;charc=getchar(); while(c>'9'||c<'0')......
  • 普通树模板
    笛卡尔树#include<bits/stdc++.h>usingnamespacestd;constintN=1e7+10;intread(){ intx=0,f=1;charc=getchar(); while(c>'9'||c<'0'){if(c=='-')f=-1;c=ge......
  • 扩散模型 (Diffusion Model) 简要介绍与源码分析
    扩散模型(DiffusionModel)简要介绍与源码分析前言近期同事分享了DiffusionModel,这才发现生成模型的发展已经到了如此惊人的地步,OpenAI推出的Dall-E2可以根据......
  • C++模板特化,Concept,typename
    typenameT,表示T为类型,而不是变量那,T::A是什么?T可以是我们自己写的类,那T::A就是成员变量或成员函数,另外,T::A还可以是类型,T内定义的类型所以,编译器需要区分,T::A到底是什么......
  • 【FreeMarker模板引擎】5.freemarker结合Struts2使用
    上一篇讲解了Freemarker与Servlet的结合,这里我们讲解一下Freemarker与Struts2的结合。同样首先创建一个WebProject工程:将Struts2的相关核心jar包和F......
  • 【FreeMarker模板引擎】4.freemarker结合Servlet使用
    之前讲解了freemarker的基础知识和数据结构,以及freemarker的样例。下面我们将结合JavaWeb和其它框架来使用freemarker作为视图框架。一、Freemark......
  • 【FreeMarker模板引擎】3.freemarker命名空间
    上一篇我们讨论了freemarker的数据结构、控制语句的基础知识和使用技巧,本篇我们介绍一下freemarker的命名空间。一、命名空间简介和使用对于“命......
  • 【Java邮件开发】4.JavaMail API的简单介绍和jar包准备
    1.前言我们之前通过使用命令行手工敲SMTP和POP3的指令,进行了邮件收发。我们接下来使用Java来实现邮件的收发。如果我们不依赖Java的邮件API,而是使用原生模拟SMTP和POP3......
  • 【JavaScript】DOM结构介绍和方法预览
    DOM1.DOM介绍DOM是DocumentObjectModel文档对象模型的缩写。根据W3CDOM规范,DOM是一种与浏览器,平台,语言无关的接口,使得你可以访问页面其他的标......
  • 李沐多模态串讲视频总结 ALBEF VLMo BLIP CoCa BEITv3 模型简要介绍
    开场多模态串讲的上篇是比较传统的多模态任务多模态最后的模态交互很重要传统的缺点是都用了预训练的目标检测器,训练和部署都很困难。ViLT把预训练的目标检......