concepts 设计指南

371 阅读2分钟

concepts设计指南

处理多个需求

例如,迭代器需求的concepts检查可能如下所示

template<typename I>

concept IsIterator =

default_initializable<T> &&

 std::copyable<I>&&

requires(I i) {

typename iter_difference_t<I>;

*i;

{++i} -> same_as<I&>;
 { i++ } →> same_as<I>; };

concepts与特征和表达

concepts不仅仅是在编译时计算布尔结果的表达式。与类型特征和其他编译时表达式相比,我通常更喜欢使用它们。

标准concepts更好地支持重载解决方案

例如,编译器能够检测一个concepts是否是另一个concepts的一部分。

考虑下面的例子,我们用两个定义为类型特征的需求重载函数foo()

template<typename T, typename U> 

requires std::is_same_v<T,U>

void foo(T,U)

{

std::cout<<"foo() for parameters of same type" <<'\n'; 

}

template<typename T, typename U>

requires std::is_same_v<T, U> && std::is_integral_v<T>

 void foo(T,U)

{

std::cout<<"foo() for integral parameters of same type"<<'\n'; }

foo(1,2);

问题是,如果两个要求都计算为 true,则两个重载都适合,并且没有规则表明其中一个优先于另一个。

因此,编译器停止编译将出现歧义错误。

如果我们改用相应的concepts,编译器会发现第二个要求是专业化,如果同时满足这两个要求,则首选它:

template<typename T, typename U> 

requires std::same_as<T, U>

//using concepts void foo(T,U)

{

std::cout<<"foo() for parameters of same type" <<'\n'; 

}

template<typename T,typename U>

requires std::same_as<T, U> && std::integral<T>

 void foo(T,U)

{

std::cout <<"foo() for integral parameters of same type"<<'\n'; 

}

foo(1,2);

谨慎定义concepts

为了检测子concepts,编译器必须能够检测到这些concepts以及它们被称为匹配的方式。这需要对concepts进行仔细的定义。例如,假设我们定义了自己的conceptsSameAs,并按如下方式使用它:

template<typename T, typename U>

concept SameAs = std::is_same_v<T,U>;

template<typename T, typename U>

requires SameAs<T,U>

void foo(T,U)

{

std::cout<<"foo() for parameters of same type" <<'\n'; }

template<typename T, typename U>

requires SameAs<T,U> && std::integral<T>

void foo(T,U)

{

std::cout<<"foo() for integral parameters of same type" <<'\n';

 }

foo(1,2);

但是,请注意,如果在第二个foo()的要求中调用SameAs<>时交换参数会发生什么情况。

template<typename T, typename U>

concept SameAs =std::is_same_v<T,U>;

template<typename T,typename U>

requires SameAs<T, U>

void foo(T,U)

{

std::cout<<"foo() for parameters of same type" <<'\n'; }

template<typename T, typename U>

requires SameAs<U, T> && std::integral<T>

void foo(T,U)

{

std::cout<<"foo() for integral parameters of same type" <<'\n'; }

foo(1,2);

问题是编译器无法检测到SameAs<>是可交换的。顺序可能很重要,因此第一个需求不一定是第二个需求的子集。

但是,我们可以通过明确SameAs<>元素的顺序不重要来解决这个问题。这需要一个助手concepts

template<typename T, typename U>

concept SameAsHelper = std::is_same_v<T, U>; template<typename T, typename U>

concept SameAs = SameAsHelper<T, U> && SameAsHelper<U, T

现在,参数的顺序对于IsSame不再重要

template<typename T,typename U> 

requires SameAs<T, U>

void foo(T,U)

{

std::cout << "foo() for parameters of same type" <<'\n'; }

template<typename T, typename U>

requires SameAs<U,T> && std::integral<T>

void foo(T,U)

{

std::cout<<"foo() for integral parameters of same type" <<'\n'; }

foo(1,2);

编译器可以发现第一个构建块SameAs<U,T>是SameAs定义的子concepts的一部分,因此其他构建块SameAs<T,U>和std::integral是一个扩展。因此,第二个foo()现在具有优先级。

c++标准知道这样的问题,因此定义了std::same as accord这个concepts,这样元素的顺序就无关紧要了

    template<typename T, typename U> requires std::same_as<T,U>

 void foo(T,U)

{

std::cout<<"foo() for parameters of same type" <<'\n'; }

template<typename T,typename U>

requires std::same_as<U, T> && std::integral<T>

 void foo(T,U)

{

std::cout <<"foo() for integral parameters of same type" <<'\n';
 }
    
foo(1,2);

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 N 天,点击查看活动详情