cppcon2021 - C++20Templates:The next level:Concepts and more
C++20 Templates: The next level - Concepts and more
该演讲内容涵盖了concepts的基础,拖着进度条也不会落下什么东西,基本是面向初学者了。
那我们先看看concepts能干什么。
Concepts
concepts可以将一个类型的需求“公式化”,你在写一个模板时,如果你对你的模板参数有什么需求,你可以使用concepts。一个concepts可包括多个concepts的定义和requirements
template<typename T, typename U>
concept MyConcept = std::same_as<T,U> &&
(std::is_class_v<T> || std::is_enum_v<T>);
这里的MyConcept是由其他concept——same_as以及requirement——is_class_v&is_enum_v组成的。
拿concept可以拿来干什么呢?
举个例子:
const auto x = Add(2,3,4,5);
const auto y = Add(2,3);
const auto z = Add(2,3.14); //error
我们想实现一个可变参数模板函数,实现任意个同类型量相加,返回他们的和。
既然我们想要模板能够自己检查出不同类型的错误,我们可以实现一个concept,不过在那之前,我们先看看假如用c++17应该怎么实现。
template<typename T,typename... Ts>
constexpr inline bool are_same_v = std::conjunction_v<std::is_same<T, Ts>...>;
template<typename T, typename...>
struct first_arg {
using type = T;
};
template<typename... Args>
using first_arg_t = first_arg<Args...>::type;
template<typename... Args>
std::enable_if_t<are_same_v<Args...>, first_arg_t<Args...>>
Add(Args&... args){
return (... + args);
}
are_same_v是检查所有参数类型是否一致,first_arg_v来获得所有参数的第一个参数类型,这里用来获取返回值。
将enable_if_v放在返回值位置,如果满足条件(即类型相同),那么返回值类型为第一个参数类型,如果不满足,编译就会报错。
(... + args)是c++的unfold运算,算是语法糖吧。
那到了c++20,实现就很简单了。
template<typename... Args>
requires are_same_v<Args...>
auto Add(Args&... args) noexcept{
return (... + args);
}
concepts的引入使得模板约束变得比以前整洁太多,可读性也很高,学习难度瀑布式下降。
concepts可使用的位置如下:
// you can use your concepts to replace the "C"
template<C T> //type-constraint
requires C<T> //requires-clause
C auto /*<-costrained placeholder type->*/ Fun(C auto param) requeires C<T> //trailing requires-clause
但是Add()函数的要求可远远不止这些。
Requires Expression
requires(T t, U u)
{
//some requirements
}
requires表达式会尝试编译表达式内的代码,查看这些代码是否合法。整个表达式可以想成返回一个bool,如果代码非法,则requires会false。
我们的add函数的参数ARGS需要以下几个要求——可加(即存在重载的operator+),类型相同,参数包至少含有一个参数,operator必须是noexcept的。
于是:
requires(Args... args)
{
(... + args);
requires are_same_v<Args...>;
requires sizeof...(Args) > 1;
{(... + args)} noexcept;
{(... + args)} -> same_as<first_arg_t<Args...>>;
}
可以发现,requires expression内部还可以嵌套requires,假如我们去掉requires,那么它只会检查代码是否合法,比如如果改成are_same_v<Args...>,那么它一直会正常生成,因为代码是没有问题的,而我们想要检查的是它是否真的是一致的。
整个requires expression必须放在requires的字句中,即:
template<typename... Args>
requires requires(Args... args)
{
(... + args);
requires are_same_v<Args...>;
requires sizeof...(Args) > 1;
{(... + args)} noexcept;
{(... + args)} -> same_as<first_arg_t<Args...>>;
}
或者,我们把它定义成一个concept
template<typename... Args>
concept Addable = requires(Args... args)
{
(... + args);
requires are_same_v<Args...>;
requires sizeof...(Args) > 1;
{(... + args)} noexcept;
{(... + args)} -> same_as<first_arg_t<Args...>>;
};
然后
template<typename... Args>
requires Addable<Args...>
auto Add(Args&... args){
return (... + args);
}
Test
那我们来测试一下
template<bool nexcept, bool operatorPlus, bool validReturnType>
struct Stub{
Stub operator+(const Stub& rhs) noexcept(nexcept)
requires(operatorPlus && validReturnType)
{return *this;}
//operatorPlus with invalid return type
int operator+(const Stub& rhs) noexcept(nexcpet)
requires(operatorPlus && not validReturnType)
{return {};}
};
using NoAdd = Stub<true,false,true>;
using ValidClass = Stub<true,true,true>;
using NotNoexcept = Stub<false,true,true>;
using DifferentReturnType = Stub<true,true,false>;
我们定义一个结构Stub用来测试,分别用三个bool值控制结构的性质,是否有operator+(),它是否是noexcept,或者返回值是否valid。然后using给各类型起个别名。
然后就来测试吧。
static_assert(not Addable<int,double>);
static_assert(not Addable<int>);
static_assert(not Addable<NoAdd,Noadd>);
static_assert(Addable<ValidClass,ValidClass>);
static_assert(Addable<int,int>);
缩写
concept的使用可以缩进参数中。
template<typename T>
void DoLock(T&& f){
std::lock_guard lock{GlobalOsMutex};
f();
} //c++17
void DoLock(std::invocable auto&& f){
std::lock_guard lock{GlobalOsMutex};
f();
}
auto的参数c++会自动生成模板。
报错信息
concepts的报错信息相当友好,平常使用过模板,发生错误的人都知道,一旦你的错误条道了模板里,在极端情况下可能会生成上百条错误信息,基本宣告放弃了。
concepts会直接指出你的错误发生在哪里,往往是不满足concepts的约束发生的。
非类型模板参数
c++20新增的关于非类型模板参数的特征有:
-
你可以使用float或double作为非类型模板参数
-
非类型模板参数有这样一种用法,我不知道怎么形容:
template<typename T> struct Non_Type{ T data; constexpr Non_Type(T t):data(t){} }; template<Non_Type U> struct Non_TypeContainer{ auto get(){return U.data;} }; Non_TypeContainer<2022> v;注意我们直接使用2022当作参数传递给
Non_TypeContainer,不过这里它会把2022自动转成非类型模板参数Non_Type的。
lambda
lambda可以使用模板了
auto max = []<typename T>(T x,T y){
return (x > y)> x: y;
};
以上,为演讲内容。

浙公网安备 33010602011771号