模板约束介绍
  ZXL8ALkrtBPG 2023年11月02日 52 0
c++


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 C1 = std::is_integral_v<T>;

template<typename T>
concept C2 = 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 C0 = std::is_integral_v<T>;

template<typename T>
concept C1 = C0<T>;

template<typename T>
concept C2 = C0<T> && sizeof(T) > 1;

此时C1的原子约束为C0,而C2包含C0,即C2包含C1,即C2更受约束;因此在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​

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
  gBkHYLY8jvYd   2023年11月19日   27   0   0 #includecic++
  ZXL8ALkrtBPG   2023年11月02日   67   0   0 c++
  ZXL8ALkrtBPG   2023年12月06日   24   0   0 字面量c++
  lh6O4DgR0ZQ8   2023年11月24日   18   0   0 cii++c++
  ZXL8ALkrtBPG   2023年11月12日   31   0   0 c++
  gBkHYLY8jvYd   2023年11月19日   27   0   0 十进制高精度c++
  ZXL8ALkrtBPG   2023年11月02日   51   0   0 c++
  ZXL8ALkrtBPG   2023年11月02日   52   0   0 c++
  gBkHYLY8jvYd   2023年12月11日   20   0   0 cic++最小值
  gBkHYLY8jvYd   2023年11月19日   25   0   0 测试点cic++
ZXL8ALkrtBPG